* 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>
232 lines
6.6 KiB
Vue
232 lines
6.6 KiB
Vue
<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>
|