feat: import worker with multiple criteria (#72)
* feat: add i18n import from file * fix: background color * feat: add import worker component * feat: support newer passport * refactor: update validator * fix: wrong endpoint * refactor: change to display passport * feat: add component and state * fix: wrong validator * refactor: add i18n * refactor: add slont top-append * refactor: use v-model * refactor: impurt workder * fix: criteria * refactor: add customer branch id --------- Co-authored-by: Thanaphon Frappet <thanaphon@frappet.com>
This commit is contained in:
parent
481d9d4e9e
commit
202e8c2e6f
7 changed files with 359 additions and 25 deletions
232
src/pages/05_quotation/ImportWorker.vue
Normal file
232
src/pages/05_quotation/ImportWorker.vue
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue';
|
||||
|
||||
import { csvToData } from 'src/utils/file';
|
||||
|
||||
import UploadFileArea from 'components/upload-file/UploadFileArea.vue';
|
||||
import DialogHeader from 'components/dialog/DialogHeader.vue';
|
||||
import { SaveButton, CancelButton } from 'components/button';
|
||||
import DialogFormContainer from 'src/components/dialog/DialogFormContainer.vue';
|
||||
|
||||
const open = defineModel<boolean>('open', { required: true });
|
||||
const payload = defineModel<{
|
||||
passport: string[] | null;
|
||||
visa: string[] | null;
|
||||
}>('data', { required: true });
|
||||
|
||||
defineEmits<{
|
||||
(e: 'importWorker', v: typeof payload.value): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
importWorker?: (v: typeof payload.value) => boolean | Promise<boolean>;
|
||||
}>();
|
||||
|
||||
const passportValidator = /[a-zA-Z]{1}[a-zA-Z0-9]{1}[0-9]{5,7}$/;
|
||||
|
||||
async function handleFileSelect(file: File[] | File) {
|
||||
const promise = [file]
|
||||
.flat()
|
||||
.map((v) => csvToData<{ passport: string; visa: string }>(v));
|
||||
|
||||
const data = await Promise.all(promise);
|
||||
|
||||
payload.value.passport = (payload.value.passport ?? []).concat(
|
||||
data
|
||||
.flat()
|
||||
.flatMap((v) =>
|
||||
v?.passport &&
|
||||
!payload.value.passport?.includes(v.passport) &&
|
||||
passportValidator.test(v.passport)
|
||||
? v.passport
|
||||
: [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const SELECT_PROPS = {
|
||||
clearable: true,
|
||||
dense: true,
|
||||
multiple: true,
|
||||
outlined: true,
|
||||
useInput: true,
|
||||
useChips: true,
|
||||
hideDropdownIcon: true,
|
||||
stackLabel: true,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogFormContainer
|
||||
v-model="open"
|
||||
@submit="
|
||||
async () => {
|
||||
if (importWorker) open = !(await importWorker(payload));
|
||||
$emit('importWorker', payload);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #header>
|
||||
<DialogHeader :title="$t('dialog.title.importWorker')" />
|
||||
</template>
|
||||
<template #footer>
|
||||
<CancelButton type="button" class="q-ml-auto" outlined v-close-popup />
|
||||
<SaveButton
|
||||
:label="$t('dialog.title.importWorker')"
|
||||
icon="mdi-plus"
|
||||
class="q-ml-sm"
|
||||
type="submit"
|
||||
solid
|
||||
/>
|
||||
</template>
|
||||
<div class="q-pa-md">
|
||||
<UploadFileArea
|
||||
:label="$t('general.importFromFile', { suffix: '(.csv)' })"
|
||||
type="button"
|
||||
accept=".csv"
|
||||
multiple
|
||||
@file="handleFileSelect"
|
||||
/>
|
||||
</div>
|
||||
<div style="flex: 1; width: 100%; overflow-y: auto" class="q-px-md q-mb-md">
|
||||
<div class="content-container">
|
||||
<q-expansion-item
|
||||
dense
|
||||
class="overflow-hidden"
|
||||
switch-toggle-side
|
||||
default-opened
|
||||
style="border-radius: var(--radius-2)"
|
||||
expand-icon="mdi-chevron-down-circle"
|
||||
header-class="surface-1"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<section class="row items-center full-width">
|
||||
<div class="row items-center q-pr-md q-py-xs">
|
||||
<span
|
||||
class="text-weight-medium q-mr-md"
|
||||
style="font-size: 18px"
|
||||
>
|
||||
{{ $t('general.passport') }}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<div class="surface-1 q-pa-md">
|
||||
<q-select
|
||||
class="fixed-height"
|
||||
v-model="payload.passport"
|
||||
v-bind="SELECT_PROPS"
|
||||
lazy-rules
|
||||
:placeholder="`${$t('general.passportNo')} ${$t('general.forExample', { example: 'G420316, P897546' })}`"
|
||||
:hint="$t('general.enterToAdd')"
|
||||
@new-value="
|
||||
(value, done) => {
|
||||
value = value.trim();
|
||||
if (passportValidator.test(value)) {
|
||||
return done(value.toUpperCase(), 'add-unique');
|
||||
}
|
||||
}
|
||||
"
|
||||
:rules="[
|
||||
(v: string[]) =>
|
||||
(!!v && v.length > 0) || $t('form.error.required'),
|
||||
]"
|
||||
new-value-mode="add-unique"
|
||||
autocomplete="off"
|
||||
>
|
||||
<template #selected-item="scope">
|
||||
<q-chip
|
||||
removable
|
||||
dense
|
||||
@remove="scope.removeAtIndex(scope.index)"
|
||||
:tabindex="scope.tabindex"
|
||||
class="q-ml-none q-pa-md"
|
||||
>
|
||||
<Icon
|
||||
icon="mdi-passport"
|
||||
class="q-mr-xs"
|
||||
style="font-size: 150%"
|
||||
color="hsla(var(--blue-9-hsl) / 1)"
|
||||
/>
|
||||
{{ scope.opt }}
|
||||
</q-chip>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
dense
|
||||
v-if="false"
|
||||
class="overflow-hidden"
|
||||
switch-toggle-side
|
||||
default-opened
|
||||
style="border-radius: var(--radius-2)"
|
||||
expand-icon="mdi-chevron-down-circle"
|
||||
header-class="surface-1"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<section class="row items-center full-width">
|
||||
<div class="row items-center q-pr-md q-py-sm">
|
||||
<span
|
||||
class="text-weight-medium q-mr-md"
|
||||
style="font-size: 18px"
|
||||
>
|
||||
{{ $t('general.visa') }}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<div class="surface-1 q-pa-md">
|
||||
<q-select
|
||||
class="fixed-height"
|
||||
v-model="payload.visa"
|
||||
v-bind="SELECT_PROPS"
|
||||
:placeholder="`${$t('general.visaNo')} ${$t('general.forExample', { example: 'G420316, V897546' })}`"
|
||||
@new-value="
|
||||
(value, done) => {
|
||||
value = value.trim();
|
||||
if (passportValidator.test(value)) {
|
||||
return done(value.toUpperCase(), 'add-unique');
|
||||
}
|
||||
}
|
||||
"
|
||||
:hint="$t('general.enterToAdd')"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
</div>
|
||||
</div>
|
||||
</DialogFormContainer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-4);
|
||||
}
|
||||
|
||||
.fixed-height {
|
||||
--_height: 10rem;
|
||||
}
|
||||
|
||||
.fixed-height :deep(.q-field__control) {
|
||||
min-height: var(--_height) !important;
|
||||
align-items: start;
|
||||
}
|
||||
.fixed-height :deep(.q-field__append) {
|
||||
min-height: var(--_height) !important;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
|
||||
color: hsl(var(--text-mute));
|
||||
}
|
||||
|
||||
:deep(
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue