Merge branch 'ui-new' into develop

This commit is contained in:
Methapon2001 2024-07-15 09:17:40 +07:00
commit b9d54d5e01
53 changed files with 6634 additions and 2322 deletions

View file

@ -1,6 +1,6 @@
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
import VueDatePicker from '@vuepic/vue-datepicker'; import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css' import '@vuepic/vue-datepicker/dist/main.css';
import GlobalDialog from 'components/GlobalDialog.vue'; import GlobalDialog from 'components/GlobalDialog.vue';
import GlobalLoading from 'components/GlobalLoading.vue'; import GlobalLoading from 'components/GlobalLoading.vue';

View file

@ -2,10 +2,21 @@
defineProps<{ defineProps<{
inactive?: boolean; inactive?: boolean;
color?: 'none' | 'hq' | 'br'; color?: 'none' | 'hq' | 'br';
data: Record<string, unknown>; data: {
branchLabelCode: string;
branchLabelName: string;
branchLabelTel: string;
branchLabelAddress: string;
branchLabelType: string;
};
metadata?: unknown; metadata?: unknown;
badgeField?: string[]; badgeField?: string[];
fieldSelected?: string[]; fieldSelected?: (
| 'branchLabelName'
| 'branchLabelAddress'
| 'branchLabelTel'
| 'branchLabelType'
)[];
footer?: boolean; footer?: boolean;
}>(); }>();
</script> </script>
@ -21,112 +32,124 @@ defineProps<{
'branch-card__hq': color === 'hq', 'branch-card__hq': color === 'hq',
'branch-card__br': color === 'br', 'branch-card__br': color === 'br',
}" }"
@click="$emit('open')"
> >
<div class="branch-card__header">
<div class="branch-card__wrapper">
<div class="branch-card__icon">
<q-icon
size="md"
style="scale: 0.8"
name="mdi-office-building-outline"
/>
</div>
</div>
<div class="branch-card__name">
<b>{{ data.branchLabelName }}</b>
<small class="branch-card__code">{{ data.branchLabelCode }}</small>
</div>
<div class="branch-card__action">
<q-btn icon="mdi-dots-vertical" size="sm" dense round flat @click.stop>
<slot name="action" />
</q-btn>
</div>
</div>
<div <div
class="branch-card__row" style="
:class="{ display: block;
'branch-card__header': i === 0, width: 100%;
'branch-card__footer': footer && i === Object.keys(data).length - 1, height: 1px;
}" background: hsla(0 0% 0% / 0.1);
v-for="([k, v], i) in Object.entries(data).filter( margin-bottom: var(--size-2);
([key], idx) => "
idx === 0 || (fieldSelected ? fieldSelected.includes(key) : true), />
)" <div
:key="k" v-for="key in fieldSelected?.sort() || [
'branchLabelAddress',
'branchLabelTel',
'branchLabelType',
]"
class="branch-card__data"
> >
<div class="branch-card__label"> <div>{{ $t(key) }}</div>
<span>{{ $t(k) }}</span> <div>{{ data[key as keyof typeof data] }}</div>
</div>
<div
class="branch-card__value"
:class="{ 'branch-card__badge': badgeField?.includes(k) }"
style="justify-content: space-between"
>
<span>{{ v }}</span>
<q-btn
:id="`${v}-view-detail`"
@click.stop="$emit('view-detail', metadata ?? data)"
:label="$t('viewDetail')"
rounded
outline
dense
style="text-wrap: nowrap; max-width: 80%"
no-caps
class="branch-card__view-detail q-px-md text-caption"
v-if="i === 0"
/>
</div>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.branch-card { .branch-card {
--_branch-card-row-fg: 0 0% 100%; --_branch-card-fg: 0 0% 100%;
--_branch-card-row-bg: var(--blue-5-hsl); --_branch-card-bg: var(--blue-5-hsl);
--_branch-badge-fg: var(--green-8-hsl); --_branch-status-color: var(--green-6-hsl);
--_branch-badge-bg: var(--green-6-hsl);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
box-shadow: var(--shadow-2); padding: var(--size-3);
& > .branch-card__row { & .branch-card__header {
display: flex;
margin-bottom: var(--size-2);
& .branch-card__icon {
background-color: hsla(var(--_branch-card-bg) / 0.15);
border-radius: 50%;
padding: var(--size-1);
position: relative;
transform: rotate(45deg);
aspect-ratio: 1;
&::after {
content: ' ';
display: block;
block-size: 0.5rem;
aspect-ratio: 1;
position: absolute;
border-radius: 50%;
right: -0.25rem;
top: calc(50% - 0.25rem);
bottom: calc(50% - 0.25rem);
background-color: hsla(var(--_branch-status-color) / 1);
}
& :deep(.q-icon) {
transform: rotate(-45deg);
color: hsla(var(--_branch-card-bg) / 1);
}
}
& .branch-card__name {
display: flex;
flex-direction: column;
flex: 1;
padding-inline: var(--size-2);
& .branch-card__code {
color: hsla(var(--text-mute) / 1);
}
}
& .branch-card__action {
margin-left: auto;
}
}
& .branch-card__data {
display: flex; display: flex;
flex-wrap: nowrap;
padding-inline: var(--size-4);
padding-block: var(--size-2); padding-block: var(--size-2);
&:last-child { & > :first-child {
flex-grow: 1; color: hsla(var(--text-mute) / 1);
} width: 0%;
min-width: 40%;
&:nth-child(2n + 1) {
background-color: hsla(var(--_branch-card-row-bg) / 0.1);
}
&.branch-card__header,
&.branch-card__footer {
color: hsl(var(--_branch-card-row-fg));
background-color: hsl(var(--_branch-card-row-bg));
&:deep(*) {
font-weight: 600;
}
}
&.branch-card__header > * {
display: flex;
align-items: center;
}
& > .branch-card__label {
min-width: 120px;
width: 30%;
}
&:not(.branch-card__header) .branch-card__label {
color: hsl(var(--stone-6-hsl));
}
& > .branch-card__value {
width: 70%;
&.branch-card__badge > span {
display: inline-block;
border-radius: 999rem;
padding-inline: var(--size-2);
color: hsl(var(--_branch-badge-fg));
background-color: hsla(var(--_branch-badge-bg) / 0.1);
}
} }
} }
&.branch-card__none { &.branch-card__none {
--_branch-card-row-bg: var(--gray-3-hsl); --_branch-card-bg: var(--gray-3-hsl);
&.branch-card__dark { &.branch-card__dark {
--_branch-card-row-bg: var(--gray-9-hsl); --_branch-card-bg: var(--gray-9-hsl);
} }
&:not(.branch-card__dark) .branch-card__header { &:not(.branch-card__dark) .branch-card__header {
@ -139,17 +162,22 @@ defineProps<{
} }
&.branch-card__hq { &.branch-card__hq {
--_branch-card-row-bg: var(--pink-6-hsl); --_branch-card-bg: var(--pink-6-hsl);
} }
&.branch-card__br { &.branch-card__br {
--_branch-card-row-bg: var(--violet-11-hsl); --_branch-card-bg: var(--violet-11-hsl);
&.branch-card__dark {
--_branch-card-bg: var(--violet-10-hsl);
}
} }
&.branch-card__inactive { &.branch-card__inactive {
--_branch-badge-fg: var(--red-4-hsl); --_branch-status-color: var(--red-4-hsl);
--_branch-badge-bg: var(--red-4-hsl); --_branch-badge-bg: var(--red-4-hsl);
filter: grayscale(40%); filter: grayscale(1);
background-color: hsl(var(--gray-6-hsl) / 0.1);
opacity: 0.5; opacity: 0.5;
} }
} }

View file

@ -139,6 +139,11 @@ const branchNo = defineModel<number>('branchNo');
class="col-6" class="col-6"
:label="$t('taxNo')" :label="$t('taxNo')"
v-model="taxNo" v-model="taxNo"
:rules="[
(val) =>
(val && val.length === 13 && /[0-9]+/.test(val)) ||
$t('formDialogInputTaxNoValidate'),
]"
/> />
<q-input <q-input
for="input-registerName" for="input-registerName"

View file

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { getRole } from 'src/services/keycloak';
import { CustomerBranch } from 'src/stores/customer/types'; import { CustomerBranch } from 'src/stores/customer/types';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
@ -23,7 +24,7 @@ const customerBranch = defineModel<{
const employeeId = defineModel<string>('employeeId'); const employeeId = defineModel<string>('employeeId');
const nrcNo = defineModel<string>('nrcNo'); const nrcNo = defineModel<string>('nrcNo');
defineProps<{ const props = defineProps<{
dense?: boolean; dense?: boolean;
outlined?: boolean; outlined?: boolean;
readonly?: boolean; readonly?: boolean;
@ -38,8 +39,6 @@ defineProps<{
defineEmits<{ defineEmits<{
(e: 'filterOwnerBranch', val: string, update: void): void; (e: 'filterOwnerBranch', val: string, update: void): void;
}>(); }>();
onMounted(async () => {});
</script> </script>
<template> <template>
@ -63,7 +62,16 @@ onMounted(async () => {});
option-value="id" option-value="id"
v-model="registeredBranchId" v-model="registeredBranchId"
:options="optionsBranch" :options="optionsBranch"
:rules="[(val) => !!val]" :rules="[
(val) => {
const roles = getRole() || [];
const isSpecialRole = ['admin', 'system', 'head_of_admin'].some(
(role) => roles.includes(role),
);
return isSpecialRole || !!val || 'กรุณากรอกข้อมูล';
},
]"
clearable
/> />
<q-input <q-input
@ -89,6 +97,11 @@ onMounted(async () => {});
class="col-6" class="col-6"
:label="$t('taxNo')" :label="$t('taxNo')"
v-model="taxNo" v-model="taxNo"
:rules="[
(val) =>
(val && val.length === 13 && /[0-9]+/.test(val)) ||
$t('formDialogInputTaxNoValidate'),
]"
/> />
<q-input <q-input

View file

@ -1,30 +1,27 @@
<script setup lang="ts"> <script lang="ts" setup>
import AppBox from 'components/app/AppBox.vue'; defineProps<{
inactive?: boolean;
import { CustomerBranch } from 'src/stores/customer/types'; color?: 'none' | 'pers' | 'corp';
data: {
withDefaults( customerBranchFormTab: number;
defineProps<{ branchName: string;
inactive?: boolean; address: string;
color?: 'none' | 'CORP' | 'PERS'; telephone: string;
businessTypePure: string;
metadata?: unknown; totalEmployee: number;
badgeField?: string[]; };
fieldSelected?: string[]; metadata?: unknown;
footer?: boolean; badgeField?: string[];
fieldSelected?: (
data: { | 'customerBranchFormTab'
customerBranchFormTab: number; | 'branchName'
branchName: string; | 'address'
address: string; | 'telephone'
telephone: string; | 'businessTypePure'
businessTypePure: string; | 'totalEmployee'
status: string; )[];
totalEmployee: number; footer?: boolean;
}; }>();
}>(),
{},
);
</script> </script>
<template> <template>
@ -34,114 +31,135 @@ withDefaults(
'branch-card__dark': $q.dark.isActive, 'branch-card__dark': $q.dark.isActive,
'branch-card__inactive': inactive, 'branch-card__inactive': inactive,
'branch-card__none': 'branch-card__none':
color !== 'CORP' && color !== 'PERS' && (!color || color === 'none'), color !== 'pers' && color !== 'corp' && (!color || color === 'none'),
'branch-card__CORP': color === 'CORP', 'branch-card__pers': color === 'pers',
'branch-card__PERS': color === 'PERS', 'branch-card__corp': color === 'corp',
}" }"
@click="$emit('open')"
> >
<div <div class="branch-card__header">
class="branch-card__row" <div class="branch-card__icon">
:class="{ <q-icon
'branch-card__header': i === 0, size="md"
'branch-card__footer': footer && i === Object.keys(data).length - 1, style="scale: 0.8"
}" name="mdi-office-building-outline"
v-for="([k, v], i) in Object.entries(data).filter(
([key], idx) =>
idx === 0 || (fieldSelected ? fieldSelected.includes(key) : true),
)"
:key="k"
>
<div class="branch-card__label">
<span>{{ $t(k) }}</span>
</div>
<div
class="branch-card__value"
:class="{ 'branch-card__badge': badgeField?.includes(k) }"
style="justify-content: space-between"
>
<span>{{ v }}</span>
<q-btn
@click.stop="$emit('view-detail', metadata ?? data)"
:label="$t('viewDetail')"
rounded
outline
dense
no-caps
class="branch-card__view-detail q-px-md text-caption"
v-if="i === 0"
/> />
</div> </div>
<div class="branch-card__name">
<b>{{ data.branchName }}</b>
<small class="branch-card__code">{{ data.branchName }}</small>
</div>
<div class="branch-card__action">
<q-btn
icon="mdi-eye-outline"
size="sm"
dense
round
flat
@click.stop="$emit('viewDetail')"
>
<slot name="action" />
</q-btn>
</div>
</div>
<div
style="
display: block;
width: 100%;
height: 1px;
background: hsla(0 0% 0% / 0.1);
margin-bottom: var(--size-2);
"
/>
<div
v-for="key in fieldSelected?.sort() || [
'customerBranchFormTab',
'branchName',
'address',
'telephone',
'businessTypePure',
'totalEmployee',
]"
class="branch-card__data"
>
<div>{{ $t(key) }}</div>
<div>{{ data[key as keyof typeof data] }}</div>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.branch-card { .branch-card {
--_branch-card-row-fg: 0 0% 100%; --_branch-card-fg: 0 0% 100%;
--_branch-card-row-bg: var(--blue-5-hsl); --_branch-card-bg: var(--blue-5-hsl);
--_branch-badge-fg: var(--green-8-hsl); --_branch-status-color: var(--green-6-hsl);
--_branch-badge-bg: var(--green-6-hsl);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
box-shadow: var(--shadow-2); padding: var(--size-3);
& > .branch-card__row { & .branch-card__header {
display: flex;
margin-bottom: var(--size-2);
& .branch-card__icon {
background-color: hsla(var(--_branch-card-bg) / 0.15);
border-radius: 50%;
padding: var(--size-1);
position: relative;
transform: rotate(45deg);
&::after {
content: ' ';
display: block;
block-size: 0.5rem;
aspect-ratio: 1;
position: absolute;
border-radius: 50%;
right: -0.25rem;
top: calc(50% - 0.25rem);
bottom: calc(50% - 0.25rem);
background-color: hsla(var(--_branch-status-color) / 1);
}
& :deep(.q-icon) {
transform: rotate(-45deg);
color: hsla(var(--_branch-card-bg) / 1);
}
}
& .branch-card__name {
display: flex;
flex-direction: column;
flex: 1;
padding-inline: var(--size-2);
& .branch-card__code {
color: hsla(var(--text-mute) / 1);
}
}
& .branch-card__action {
margin-left: auto;
}
}
& .branch-card__data {
display: flex; display: flex;
flex-wrap: nowrap;
padding-inline: var(--size-4);
padding-block: var(--size-2); padding-block: var(--size-2);
&:last-child { & > :first-child {
flex-grow: 1; color: hsla(var(--text-mute) / 1);
} width: 0%;
min-width: 40%;
&:nth-child(2n + 1) {
background-color: hsla(var(--_branch-card-row-bg) / 0.05);
}
&.branch-card__header,
&.branch-card__footer {
color: hsl(var(--_branch-card-row-fg));
background-color: hsl(var(--_branch-card-row-bg));
&:deep(*) {
font-weight: 600;
}
}
&.branch-card__header > * {
display: flex;
align-items: center;
}
& > .branch-card__label {
min-width: 120px;
width: 30%;
}
&:not(.branch-card__header) .branch-card__label {
color: hsl(var(--stone-6-hsl));
}
& > .branch-card__value {
width: 70%;
&.branch-card__badge > span {
display: inline-block;
border-radius: 999rem;
padding-inline: var(--size-2);
color: hsl(var(--_branch-badge-fg));
background-color: hsla(var(--_branch-badge-bg) / 0.1);
}
} }
} }
&.branch-card__none { &.branch-card__none {
--_branch-card-row-bg: var(--gray-3-hsl); --_branch-card-bg: var(--gray-3-hsl);
&.branch-card__dark { &.branch-card__dark {
--_branch-card-row-bg: var(--gray-9-hsl); --_branch-card-bg: var(--gray-9-hsl);
} }
&:not(.branch-card__dark) .branch-card__header { &:not(.branch-card__dark) .branch-card__header {
@ -153,18 +171,23 @@ withDefaults(
} }
} }
&.branch-card__CORP { &.branch-card__pers {
--_branch-card-row-bg: var(--purple-11-hsl); --_branch-card-bg: var(--pink-6-hsl);
} }
&.branch-card__PERS { &.branch-card__corp {
--_branch-card-row-bg: var(--teal-9-hsl); --_branch-card-bg: var(--violet-11-hsl);
&.branch-card__dark {
--_branch-card-bg: var(--violet-10-hsl);
}
} }
&.branch-card__inactive { &.branch-card__inactive {
--_branch-badge-fg: var(--red-4-hsl); --_branch-status-color: var(--red-4-hsl);
--_branch-badge-bg: var(--red-4-hsl); --_branch-badge-bg: var(--red-4-hsl);
filter: grayscale(40%); filter: grayscale(1);
background-color: hsl(var(--gray-6-hsl) / 0.1);
opacity: 0.5; opacity: 0.5;
} }
} }

View file

@ -3,6 +3,7 @@ import { ref, onMounted, watch } from 'vue';
import useCustomerStore from 'src/stores/customer'; import useCustomerStore from 'src/stores/customer';
import useFlowStore from 'src/stores/flow'; import useFlowStore from 'src/stores/flow';
import useOptionStore from 'src/stores/options';
import { Status } from 'src/stores/types'; import { Status } from 'src/stores/types';
import { CustomerBranch, CustomerType } from 'src/stores/customer/types'; import { CustomerBranch, CustomerType } from 'src/stores/customer/types';
@ -45,9 +46,9 @@ const prop = withDefaults(
}, },
); );
const emit = defineEmits<{ defineEmits<{
(e: 'back'): void; (e: 'back'): void;
(e: 'viewDetail', branch: CustomerBranch[]): void; (e: 'viewDetail', branch: CustomerBranch, index: number): void;
(e: 'dialog'): void; (e: 'dialog'): void;
}>(); }>();
@ -185,31 +186,29 @@ watch(currentStatus, async () => {
</div> </div>
</div> </div>
<div <div
class="row q-pa-lg q-col-gutter-xl" class="row q-pa-md q-col-gutter-md"
:class="{ 'justify-center': totalBranch === 0 }" :class="{ 'justify-center': totalBranch === 0 }"
style="min-height: 350px" style="min-height: 350px"
> >
<NoData v-if="totalBranch === 0" :not-found="!!inputSearch" /> <NoData v-if="totalBranch === 0" :not-found="!!inputSearch" />
<div <div v-for="(br, i) in branch" :key="i" class="col-4">
v-for="(br, i) in branch?.sort((a, b) => a.branchNo - b.branchNo)"
:key="i"
class="col-4"
>
<BranchCardCustomer <BranchCardCustomer
:inactive="br.status === 'INACTIVE'" :inactive="br.status === 'INACTIVE'"
:color="customerType" :color="customerType === 'CORP' ? 'corp' : 'pers'"
:badgeField="['status']" :badgeField="['status']"
:data="{ :data="{
customerBranchFormTab: br.branchNo, customerBranchFormTab: br.branchNo,
branchName: br.name, branchName: br.name,
address: br.address, address:
$i18n.locale === 'en-US'
? `${br.addressEN || ''} ${br.subDistrict?.nameEN || ''} ${br.district?.nameEN || ''} ${br.province?.nameEN || ''}`
: `${br.address || ''} ${br.subDistrict?.name || ''} ${br.district?.name || ''} ${br.province?.name || ''}`,
telephone: br.telephoneNo, telephone: br.telephoneNo,
businessTypePure: br.bussinessType, businessTypePure: useOptionStore().mapOption(br.bussinessType),
status: 'ดำเนินการอยู่',
totalEmployee: br._count?.employee, totalEmployee: br._count?.employee,
}" }"
@view-detail="emit('viewDetail', [br])" @view-detail="$emit('viewDetail', br, i)"
/> />
</div> </div>
</div> </div>

View file

@ -68,7 +68,7 @@ onMounted(async () => {
<q-select <q-select
id="select-business-type" id="select-business-type"
emit-value emit-value
option-value="label" option-value="value"
option-label="label" option-label="label"
map-options map-options
:dense="dense" :dense="dense"
@ -95,7 +95,7 @@ onMounted(async () => {
<q-select <q-select
id="select-job-position" id="select-job-position"
emit-value emit-value
option-value="label" option-value="value"
option-label="label" option-label="label"
map-options map-options
:dense="dense" :dense="dense"

View file

@ -73,10 +73,7 @@ defineProps<{
class="col-6" class="col-6"
:label="$t('formDialogInputPassportRef')" :label="$t('formDialogInputPassportRef')"
v-model="previousPassportReference" v-model="previousPassportReference"
:rules="[
(val: string) =>
!!val || $t('inputValidate') + $t('formDialogInputPassportRef'),
]"
/> />
<q-input <q-input
for="input-passport-place" for="input-passport-place"

View file

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { getRole } from 'src/services/keycloak';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { locale } = useI18n({ useScope: 'global' }); const { locale } = useI18n({ useScope: 'global' });
@ -10,9 +11,9 @@ const name = defineModel<string>('name');
const code = defineModel<string>('code'); const code = defineModel<string>('code');
const registeredBranchId = defineModel<string>('registeredBranchId'); const registeredBranchId = defineModel<string>('registeredBranchId');
const codeOption = ref([]); const codeOption = ref<{ id: string; name: string }[]>([]);
defineProps<{ const props = defineProps<{
dense?: boolean; dense?: boolean;
outlined?: boolean; outlined?: boolean;
readonly?: boolean; readonly?: boolean;
@ -31,6 +32,15 @@ onMounted(async () => {
if (locale.value === 'th-th') { if (locale.value === 'th-th') {
codeOption.value = option.tha.typeProduct; codeOption.value = option.tha.typeProduct;
} }
if(!!props.optionsBranch) {
registeredBranchId.value = props.optionsBranch[0].id ;
}
}); });
</script> </script>
@ -71,18 +81,25 @@ onMounted(async () => {
map-options map-options
options-dense options-dense
:label="$t('registeredBranch')" :label="$t('registeredBranch')"
class="col-3" class="col-3 "
option-label="name" option-label="name"
option-value="id" option-value="id"
v-model="registeredBranchId" v-model="registeredBranchId"
:options="optionsBranch" :options="optionsBranch"
:rules="[(val) => !!val]" :rules="[
(val) => {
const roles = getRole() || [];
const isSpecialRole = ['admin', 'system', 'head_of_admin'].some(role => roles.includes(role));
return isSpecialRole || !!val || 'กรุณากรอกข้อมูล';
}
]"
clearable
/> />
<q-input <q-input
for="input-name" for="input-name"
:dense="dense" :dense="dense"
:outlined="!readonly" outlined
:readonly="readonly" :readonly="readonly"
:borderless="readonly" :borderless="readonly"
hide-bottom-space hide-bottom-space
@ -94,7 +111,7 @@ onMounted(async () => {
<q-input <q-input
for="input-detail" for="input-detail"
:dense="dense" :dense="dense"
:outlined="!readonly" outlined
:readonly="readonly" :readonly="readonly"
:borderless="readonly" :borderless="readonly"
hide-bottom-space hide-bottom-space
@ -108,7 +125,7 @@ onMounted(async () => {
<div class="col-12"> <div class="col-12">
<q-field <q-field
class="full-width" class="full-width"
:outlined="!readonly" outlined
:readonly="readonly" :readonly="readonly"
:borderless="readonly" :borderless="readonly"
:label="$t('detail')" :label="$t('detail')"
@ -138,12 +155,12 @@ onMounted(async () => {
<q-input <q-input
for="input-process" for="input-process"
:dense="dense" :dense="dense"
:outlined="!readonly" outlined
:readonly="readonly" :readonly="readonly"
:borderless="readonly" :borderless="readonly"
hide-bottom-space hide-bottom-space
class="col-4" class="col-4"
:label="$t('processingTime')" :label="$t('productProcessingTime')"
v-model="process" v-model="process"
type="number" type="number"
/> />

View file

@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { getRole } from 'src/services/keycloak';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { onMounted } from 'vue';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
@ -13,7 +15,7 @@ const serviceName = defineModel<string>('serviceNameTh');
const serviceDescription = defineModel<string>('serviceDescription'); const serviceDescription = defineModel<string>('serviceDescription');
const registeredBranchId = defineModel<string | null>('registeredBranchId'); const registeredBranchId = defineModel<string | null>('registeredBranchId');
defineProps<{ const props = defineProps<{
dense?: boolean; dense?: boolean;
outlined?: boolean; outlined?: boolean;
readonly?: boolean; readonly?: boolean;
@ -77,6 +79,7 @@ defineProps<{
<q-select <q-select
id="input-source-nationality" id="input-source-nationality"
:dense="dense" :dense="dense"
outlined outlined
:readonly="readonly" :readonly="readonly"
@ -86,12 +89,19 @@ defineProps<{
map-options map-options
options-dense options-dense
:label="$t('registeredBranch')" :label="$t('registeredBranch')"
class="col-3" class="col-3 "
option-label="name" option-label="name"
option-value="id" option-value="id"
v-model="registeredBranchId" v-model="registeredBranchId"
:options="optionsBranch" :options="optionsBranch"
:rules="[(val) => !!val]" :rules="[
(val) => {
const roles = getRole() || [];
const isSpecialRole = ['admin', 'system', 'head_of_admin'].some(role => roles.includes(role));
return isSpecialRole || !!val || 'กรุณากรอกข้อมูล';
}
]"
clearable
/> />
<q-input <q-input

View file

@ -13,37 +13,40 @@ defineProps<{
</script> </script>
<template> <template>
<div class="col-3 app-text-muted"> {{ $t('priceeInformation') }}</div> <div class="col-3 app-text-muted"> {{ $t('priceInformation') }}</div>
<div class="col-9 row q-col-gutter-md"> <div class="col-9 row q-col-gutter-md">
<q-input <q-input
:dense="dense" :dense="dense"
:outlined="!readonly" outlined
:readonly="readonly" :readonly="readonly"
:borderless="readonly" :borderless="readonly"
hide-bottom-space hide-bottom-space
class="col-4" class="col-4"
type="number"
:label="$t('salePrice')" :label="$t('salePrice')"
v-model="price" v-model="price"
/> />
<q-input <q-input
:dense="dense" :dense="dense"
:outlined="!readonly" outlined
:readonly="readonly" :readonly="readonly"
:borderless="readonly" :borderless="readonly"
hide-bottom-space hide-bottom-space
class="col-4" class="col-4"
type="number"
:label="$t('agentPrice')" :label="$t('agentPrice')"
v-model="agentPrice" v-model="agentPrice"
/> />
<q-input <q-input
:dense="dense" :dense="dense"
:outlined="!readonly" outlined
:readonly="readonly" :readonly="readonly"
:borderless="readonly" :borderless="readonly"
hide-bottom-space hide-bottom-space
class="col-4" class="col-4"
type="number"
:label="$t('processingPrice')" :label="$t('processingPrice')"
v-model="serviceCharge" v-model="serviceCharge"
/> />

View file

@ -37,7 +37,7 @@ withDefaults(
> >
<div <div
class="relative-position rounded q-pb-lg" class="relative-position rounded q-pb-lg"
:style="`background-color:hsla(${color}/0.1)`" :style="`background-color:hsla(${color}/.2)`"
> >
<div class="row justify-between items-center"> <div class="row justify-between items-center">
<q-avatar <q-avatar

View file

@ -42,8 +42,8 @@ withDefaults(
<div <div
bordered bordered
:class="{ 'is-add-product': isAddProduct }" :class="{ 'is-add-product': isAddProduct }"
class="column bordered rounded q-pa-sm no-wrap" class="column bordered rounded q-pa-sm no-wrap surface-1"
style="box-shadow: var(--shadow-3); height: 20rem" style="height: 20rem"
@click="$emit('select', data)" @click="$emit('select', data)"
> >
<div class="row flex justify-between text-bold"> <div class="row flex justify-between text-bold">

View file

@ -9,6 +9,7 @@ defineProps<{
<div class="row"> <div class="row">
<q-btn <q-btn
unelevated unelevated
id="btn-Add"
@click="$emit('trigger')" @click="$emit('trigger')"
size="lg" size="lg"
class="color-btn" class="color-btn"

View file

@ -1,7 +1,7 @@
<script setup lang="ts"></script> <script setup lang="ts"></script>
<template> <template>
<q-page-sticky position="bottom-right" :offset="[30, 18]"> <q-page-sticky position="bottom-right" :offset="[8, 8]">
<q-fab <q-fab
id="btn-add" id="btn-add"
padding="xs" padding="xs"
@ -9,9 +9,9 @@
direction="up" direction="up"
color="primary" color="primary"
> >
<slot /> <slot>
<q-fab-action padding="xs" color="primary" icon="mdi-account-plus" />
<!-- <q-fab-action padding="xs" color="primary" icon="mdi-account-plus" /> --> </slot>
</q-fab> </q-fab>
</q-page-sticky> </q-page-sticky>
</template> </template>

View file

@ -1,9 +1,32 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue'; import { computed, ref, onMounted } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import useMyBranch from 'stores/my-branch';
import { getUserId, getRole } from 'src/services/keycloak';
defineProps<{
mini?: boolean;
}>();
onMounted(async () => {
const uid = getUserId();
const role = getRole();
if (!uid) return;
if (role?.includes('system')) {
const result = await userBranch.fetchListOptionBranch();
if (result && result.total > 0) currentMyBranch.value = result.result[0];
}
const result = await userBranch.fetchListMyBranch(uid);
if (result && result.total > 0) currentMyBranch.value = result.result[0];
});
const router = useRouter(); const router = useRouter();
const userBranch = useMyBranch();
const { currentMyBranch } = storeToRefs(userBranch);
const currentRoute = ref<string>(''); const currentRoute = ref<string>('');
const currentPath = computed(() => { const currentPath = computed(() => {
@ -79,55 +102,119 @@ const labelMenu = ref<
]); ]);
const leftDrawerOpen = defineModel<boolean>('leftDrawerOpen', { const leftDrawerOpen = defineModel<boolean>('leftDrawerOpen', {
default: false, default: true,
}); });
function navigateTo(label: string, destination: string) { function navigateTo(label: string, destination: string) {
currentRoute.value = label; currentRoute.value = label;
router.push(`${destination}`); router.push(`${destination}`);
} }
function branchSetting() {}
</script> </script>
<template> <template>
<!-- Drawer --> <!-- Drawer -->
<q-drawer <q-drawer
no-swipe-open
v-model="leftDrawerOpen" v-model="leftDrawerOpen"
side="left" side="left"
:breakpoint="599" :breakpoint="599"
:width="$q.screen.lt.md ? 200 : 300" class="column justify-between no-wrap"
:width="mini ? 80 : 256"
show-if-above
> >
<div class="main-bar"> <!-- :width="$q.screen.lt.sm ? $q.screen.width - 16 : 256" -->
<div class="scroll" style="overflow-x: hidden; scrollbar-gutter: stable">
<div <div
class="column items-center justify-center q-pa-xl cursor-pointer" class="flex justify-center items-center q-pl-sm cursor-pointer"
@click="$router.push('/')" @click="$router.push('/')"
id="btn-drawer-home" id="btn-drawer-home"
style="height: 128px"
> >
<q-img src="/logo.png" style="width: 70%; height: 70%"> <q-img
<template #error> fit="contain"
<img src="/logo.png" alt="" width="100%" /> :src="mini ? 'logo_jws.png' : '/logo.png'"
</template> :style="{ filter: `brightness(${$q.dark.isActive ? '1.3' : '1'})` }"
</q-img> style="height: 64px"
/>
</div>
<div id="drawer-menu" class="q-pl-md q-mr-xs">
<q-item
v-for="v in labelMenu"
dense
clickable
class="row items-center q-my-xs q-px-xs"
:key="v.label"
:disable="!!v.disabled"
:class="{
active: currentPath === v.route,
'border-active': currentPath === v.route,
dark: $q.dark.isActive,
}"
@click="navigateTo(v.label, v.route)"
>
<div class="row justify-center items-center">
<Icon :icon="v.icon" width="24px" />
<q-tooltip v-if="mini">
{{ $t(v.label) }}
</q-tooltip>
</div>
<div v-if="!mini" class="q-ml-md" style="white-space: nowrap">
{{ $t(v.label) }}
</div>
</q-item>
</div> </div>
</div> </div>
<div id="drawer-menu" class="q-pr-sm"> <div
<q-item class="surface-2 q-px-md q-py-sm row items-center no-wrap justify-start"
v-for="v in labelMenu" :class="{ 'justify-center': mini }"
:key="v.label" style="min-height: 56px; overflow-x: hidden"
clickable >
:disable="!!v.disabled" <q-avatar
@click="navigateTo(v.label, v.route)" text-color="white"
class="no-padding" size="md"
:class="{ active: currentPath === v.route, dark: $q.dark.isActive }" :style="`background-color: hsla(var(--violet-${$q.dark.isActive ? '10' : '11'}-hsl)/0.15)`"
class="relative-position"
> >
<q-item-section id="btn-drawer-back "> <q-icon
<q-item-label class="q-pl-lg row items-center"> name="mdi-home-group"
<div class="box-border-left" /> size="sm"
<Icon :icon="v.icon" width="24px" class="q-mr-md" /> :style="`color: var(--violet-${$q.dark.isActive ? '10' : '11'})`"
{{ $t(v.label) }} />
</q-item-label> <div class="dot absolute-bottom-right" />
</q-item-section> </q-avatar>
</q-item>
<div
v-if="!mini"
class="text-caption column q-ml-sm"
style="white-space: nowrap"
>
<span class="text-weight-bold">
{{
($i18n.locale === 'en-US'
? currentMyBranch?.nameEN
: currentMyBranch?.name) ?? '-'
}}
</span>
<span>
{{ currentMyBranch?.code ?? '-' }}
</span>
</div>
<q-btn
v-if="!mini"
dense
flat
rounded
icon="mdi-cog-outline"
size="sm"
@click="branchSetting"
style="margin-left: auto"
/>
</div> </div>
</q-drawer> </q-drawer>
</template> </template>
@ -135,27 +222,47 @@ function navigateTo(label: string, destination: string) {
<style lang="scss" scoped> <style lang="scss" scoped>
#drawer-menu :deep(.q-item) { #drawer-menu :deep(.q-item) {
color: var(--gray-6); color: var(--gray-6);
border-top-right-radius: 10px; border-radius: var(--radius-2);
border-bottom-right-radius: 10px;
} }
#drawer-menu :deep(.q-item.active) { #drawer-menu :deep(.q-item.active) {
--_drawer-item-background-color: var(--brand-1) !important; --_drawer-item-background-color: var(--brand-1) !important;
background-color: var(--_drawer-item-background-color); background-color: var(--_drawer-item-background-color);
color: white; color: white;
border-left: 10px solid $secondary;
&.dark { // border-left: 10px solid $secondary;
--_drawer-item-background-color: var(--gray-10) !important; }
border: 1px solid var(--brand-1);
border-left: 10px solid $secondary; #drawer-menu :deep(.q-item.active)::before {
.box-border-left { display: block;
position: absolute; position: absolute;
width: 10px; content: ' ';
background: $secondary; background: var(--brand-2);
height: 48.5px; border-radius: 99rem;
top: -1px; width: 6px;
left: -10px; left: -0.6rem;
} top: 18%;
} bottom: 18%;
cursor: context-menu;
}
:deep(.q-drawer) {
transition: 0.1s width ease-in-out;
background-color: transparent;
padding: var(--size-4);
padding-right: 0;
}
:deep(.q-drawer__content) {
border-radius: var(--radius-2);
background: var(--surface-1);
}
.dot {
height: 10px;
width: 10px;
background-color: var(--teal-6);
border-radius: 50%;
display: inline-block;
border: 1.5px solid white;
} }
</style> </style>

View file

@ -1,16 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import AppBox from 'components/app/AppBox.vue';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
branch: { branch: {
icon: string;
count: number; count: number;
label: string; label: string;
color: 'pink' | 'purple' | 'green' | 'orange'; color:
| 'pink'
| 'purple'
| 'green'
| 'orange'
| 'cyan'
| 'yellow'
| 'red'
| 'magenta';
}[]; }[];
dark?: boolean; dark?: boolean;
labelI18n?: boolean; labelI18n?: boolean;
nowrap?: boolean;
}>(), }>(),
{ {
labelI18n: false, labelI18n: false,
@ -19,113 +25,101 @@ const props = withDefaults(
</script> </script>
<template> <template>
<div <div class="row no-wrap" style="gap: var(--size-4)" :class="{ dark }">
class="row full-width" <div
style="gap: var(--size-4)"
:class="{ dark, 'no-wrap': nowrap }"
>
<AppBox
v-for="v in props.branch" v-for="v in props.branch"
:key="v.label" class="no-padding stat-card"
class="bordered stat-card__wave"
:class="{ [`stat-card__${v.color}`]: true }" :class="{ [`stat-card__${v.color}`]: true }"
style=" :key="v.label"
padding: 12px 16px;
height: 75px;
min-width: 232px;
box-shadow: var(--shadow-2);
"
> >
<svg <div class="stat-card__content row items-center q-pa-sm">
xmlns="http://www.w3.org/2000/svg" <div class="col-4 text-center">
viewBox="0 0 1440 320" <q-avatar
style="width: 100%" size="lg"
> style="background-color: hsla(0 0% 100% /0.2)"
<path text-color="white"
class="box-path" :icon="v.icon"
d="M0,256L40,245.3C80,235,160,213,240,202.7C320,192,400,192,480,170.7C560,149,640,107,720,106.7C800,107,880,149,960,154.7C1040,160,1120,128,1200,96C1280,64,1360,32,1400,16L1440,0L1440,320L1400,320C1360,320,1280,320,1200,320C1120,320,1040,320,960,320C880,320,800,320,720,320C640,320,560,320,480,320C400,320,320,320,240,320C160,320,80,320,40,320L0,320Z" />
></path> </div>
</svg> <div class="col-6 justify-center column">
<div class="col-6 ellipsis text-bold" style="width: 100%">
{{ labelI18n ? $t(v.label) : v.label }}
<q-tooltip
anchor="top middle"
self="bottom middle"
:offset="[10, 10]"
>
{{ labelI18n ? $t(v.label) : v.label }}
</q-tooltip>
</div>
<div class="stat-card__content"> <div class="col-6">{{ v.count }}</div>
<div class="text-h5 text-weight-bold">{{ v.count }}</div>
<div class="text-weight-bold text-no-wrap">
{{ labelI18n ? $t(v.label) : v.label }}
</div> </div>
</div> </div>
</AppBox> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.stat-card { .stat-card {
--_color: var(--gray-6); border-radius: var(--radius-2);
display: grid; overflow: hidden;
grid-template-columns: repeat(2, 1fr); box-shadow: var(--shadow-2);
}
@media screen and (min-width: 1024px) {
.stat-card {
display: flex;
}
}
@media screen and (min-width: 1920px) {
.stat-card {
display: flex;
}
}
.stat-card__wave {
position: relative;
display: flex;
& svg {
width: 200px;
position: absolute;
left: 0px;
bottom: 0;
opacity: 0.1;
& .box-path {
fill: var(--_color);
fill-opacity: 1;
}
}
} }
.stat-card__content { .stat-card__content {
z-index: 2; z-index: 2;
color: var(--_color); color: hsla(var(--gray-0-hsl) / 1);
background: hsla(var(--_color) / 1);
height: 100%; height: 100%;
width: 200px; min-width: 200px;
} }
.stat-card__purple { .stat-card__purple {
--_color: var(--violet-11); --_color: var(--violet-11-hsl);
} }
.stat-card__pink { .stat-card__pink {
--_color: var(--pink-6); --_color: var(--pink-6-hsl);
} }
.stat-card__green { .stat-card__green {
--_color: var(--teal-10); --_color: var(--teal-10-hsl);
}
.stat-card__cyan {
--_color: var(--cyan-7-hsl);
}
.stat-card__red {
--_color: var(--red-6-hsl);
}
.stat-card__yellow {
--_color: var(--orange-4-hsl);
} }
.stat-card__orange { .stat-card__orange {
--_color: var(--orange-5); --_color: var(--orange-5-hsl);
}
.stat-card__magenta {
--_color: var(--pink-8-hsl);
} }
.dark .stat-card__purple { .dark .stat-card__purple {
--_color: var(--purple-7); --_color: var(--violet-10-hsl);
} }
.dark .stat-card__green { .dark .stat-card__green {
--_color: var(--teal-7); --_color: var(--teal-8-hsl);
} }
.dark .stat-card__orange { .dark .stat-card__orange {
--_color: var(--orange-8); --_color: var(--orange-6-hsl);
}
.dark .stat-card__magenta {
--_color: var(--pink-7-hsl);
} }
</style> </style>

View file

@ -4,6 +4,7 @@ import { CustomerBranchCreate } from 'stores/customer/types';
defineProps<{ defineProps<{
readonly?: boolean; readonly?: boolean;
edit?: boolean;
}>(); }>();
const customerBranch = defineModel<CustomerBranchCreate[]>('customerBranch', { const customerBranch = defineModel<CustomerBranchCreate[]>('customerBranch', {
@ -17,6 +18,8 @@ const index = ref<number>(0);
function addData() { function addData() {
index.value++; index.value++;
customerBranch.value.push({ customerBranch.value.push({
code: '',
branchNo: undefined,
address: '', address: '',
addressEN: '', addressEN: '',
provinceId: '', provinceId: '',
@ -91,7 +94,8 @@ function close(index: number) {
v-for="(v, index) in customerBranch" v-for="(v, index) in customerBranch"
:key="index" :key="index"
:name="index" :name="index"
:label="`${$t('customerBranchFormTab')} ${index + 1}`" :label="`${$t('customerBranchFormTab')} `"
:disable="tab !== index && edit"
@click="tab = index" @click="tab = index"
no-caps no-caps
:class="tab === index ? '' : 'bordered-b bordered-r'" :class="tab === index ? '' : 'bordered-b bordered-r'"
@ -114,7 +118,9 @@ function close(index: number) {
<q-tab-panels v-model="tab" class="rounded-borders"> <q-tab-panels v-model="tab" class="rounded-borders">
<q-tab-panel <q-tab-panel
:id="`tab-branch-${index}`" :id="`tab-branch-${index}`"
v-for="(v, index) in customerBranch" v-for="(v, index) in customerBranch.sort(
(a, b) => (a.branchNo ?? 0) - (b.branchNo ?? 0),
)"
:key="index" :key="index"
:name="index" :name="index"
> >

View file

@ -315,11 +315,11 @@ defineEmits<{
} }
} }
&.color__purple { &.color__purple {
--_color: var(--purple-11-hsl); --_color: var(--violet-11-hsl);
} }
&.color__green { &.color__green {
--_color: var(--teal-9-hsl); --_color: var(--teal-10-hsl);
} }
} }

View file

@ -5,275 +5,242 @@ import AppBox from 'components/app/AppBox.vue';
import AppCircle from 'components/app/AppCircle.vue'; import AppCircle from 'components/app/AppCircle.vue';
defineProps<{ defineProps<{
list: { data: {
id: string;
name: string; name: string;
detail?: { label: string; value: string }[]; code: string;
male?: boolean; male?: boolean;
female?: boolean; female?: boolean;
disabled?: boolean;
badge?: string;
img?: string; img?: string;
}[]; detail?: { icon: string; value: string }[];
gridColumns?: number; };
tag?: [{ color: string; value: string }];
disabled?: boolean;
noHover?: boolean; noHover?: boolean;
noAction?: boolean; noAction?: boolean;
noDetail?: boolean;
noBg?: boolean; noBg?: boolean;
detailColumnCount?: number;
canEditProfile?: boolean;
history?: boolean; history?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
(e: 'editProfile'): void; (e: 'editProfile'): void;
(e: 'history', id: string): void; (e: 'history'): void;
(e: 'deleteCard', id: string): void; (e: 'deleteCard'): void;
( (e: 'updateCard', action: 'FORM' | 'INFO', isEdit?: boolean): void;
e: 'updateCard', (e: 'enterCard', action: 'FORM' | 'INFO'): void;
action: 'FORM' | 'INFO', (e: 'toggleStatus', status: boolean): void;
id: string,
isEdit?: boolean,
): void;
(e: 'enterCard', action: 'FORM' | 'INFO', id: string): void;
(e: 'toggleStatus', id: string, status: boolean): void;
}>(); }>();
</script> </script>
<template> <template>
<div <AppBox
class="person-container" bordered
:style="`grid-template-columns: repeat( ${ no-padding
gridColumns class="column person-box"
? `${gridColumns}, 1fr` :class="{
: $q.screen.gt.sm 'person-box__disabled': disabled,
? '4, 1fr' 'person-box__no-hover': noHover,
: $q.screen.gt.xs 'person-box__no-detail': !data.detail,
? '2, 1fr' 'person-box__no-bg': noBg,
: '1, 1fr' }"
})`" @click="$emit('enterCard', 'INFO')"
> >
<AppBox <div class="column items-center">
bordered <!-- kebab menu -->
class="column person-box" <div class="full-width flex" v-if="!noAction">
:class="{ <div style="margin-right: auto">
'person-box__disabled': v.disabled, <span
'person-box__no-hover': noHover, class="tags"
'person-box__no-detail': noDetail, v-for="v in tag"
'person-box__no-bg': noBg, :class="{ [`tags__${v.color}`]: true }"
}"
@click="$emit('enterCard', 'INFO', v.id)"
v-for="(v, i) in list"
:key="i"
>
<div class="q-pa-sm column items-center">
<!-- kebab menu -->
<div class="full-width text-right" v-if="!noAction">
<q-btn
v-if="history"
flat
round
class="app-text-muted-2"
padding="xs"
icon="mdi-history"
size="sm"
@click.stop="$emit('history', v.id)"
/>
<q-btn
flat
round
padding="xs"
class="app-text-muted-2"
icon="mdi-dots-vertical"
size="sm"
@click.stop
> >
<q-menu class="bordered"> {{ v.value }}
<q-list v-close-popup>
<q-item
clickable
dense
class="row q-py-sm"
style="white-space: nowrap"
@click="$emit('enterCard', 'INFO', v.id)"
>
<q-icon
name="mdi-eye-outline"
class="col-3"
size="xs"
style="color: hsl(var(--green-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('viewDetail') }}
</span>
</q-item>
<q-item
dense
clickable
class="row q-py-sm"
style="white-space: nowrap"
@click="$emit('updateCard', 'INFO', v.id, true)"
v-close-popup
>
<q-icon
name="mdi-pencil-outline"
class="col-3"
size="xs"
style="color: hsl(var(--cyan-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('edit') }}
</span>
</q-item>
<q-item
dense
clickable
@click="$emit('deleteCard', v.id)"
v-close-popup
>
<q-icon
name="mdi-trash-can-outline"
size="xs"
class="col-3 app-text-negative"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('delete') }}
</span>
</q-item>
<q-item dense>
<q-item-section class="q-py-sm">
<div class="q-pa-sm surface-2 rounded">
<q-toggle
dense
size="sm"
@click="
$emit('toggleStatus', v.id, v.disabled === false)
"
:model-value="!v.disabled"
val="xs"
padding="none"
>
<div class="q-ml-xs">
{{
!v.disabled
? $t('switchOnLabel')
: $t('switchOffLabel')
}}
</div>
</q-toggle>
</div>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<!-- profile -->
<div style="position: relative">
<AppCircle
bordered
class="avatar"
style="border: 2px solid var(--border-color)"
:class="{ 'edit-profile': canEditProfile }"
@click="
() => {
if (canEditProfile) $emit('editProfile');
}
"
>
<q-img
:src="v.img"
style="object-fit: cover; width: 100%; height: 100%"
/>
<div class="status-circle">
<q-icon
:name="`mdi-${v.disabled ? 'close' : 'check'}`"
:style="`color:${v.disabled ? 'var(--gray-6)' : 'white'}`"
/>
</div>
</AppCircle>
</div>
<!-- name symbol -->
<span class="items-center row q-my-sm">
{{ v.name }}
<Icon
class="q-pl-sm"
:class="{
'symbol-gender': v.male || v.female,
'symbol-gender__male': !v.disabled && v.male,
'symbol-gender__female': !v.disabled && v.female,
'symbol-gender__disable': v.disabled,
}"
:icon="`material-symbols:${v.male ? 'male' : 'female'}`"
width="24px"
/>
<!-- <Icon class="locale q-pl-sm" icon="circle-flags:th" width="24px" /> -->
</span>
<span
v-if="v.badge"
class="badge q-px-sm"
:class="{
'bg-gender': v.male || v.female,
'bg-gender__male': !v.disabled && v.male,
'bg-gender__female': !v.disabled && v.female,
'bg-gender__disable': v.disabled,
empty: !v.male && !v.female,
}"
>
{{ v.badge }}
</span>
</div>
<!-- detail -->
<q-separator v-if="!noDetail" />
<div
v-if="!noDetail"
class="q-pt-sm q-px-md q-pb-md person-detail rounded-b full-width"
:class="{
'bg-gender': v.male || v.female,
'bg-gender__light':
(!v.disabled && v.male) || (!v.disabled && v.female),
'bg-gender__male': !v.disabled && v.male,
'bg-gender__female': !v.disabled && v.female,
'bg-gender__disable': v.disabled,
}"
:style="
(detailColumnCount &&
`grid-template-columns: repeat(${detailColumnCount}, 1fr);`) ||
''
"
>
<div v-for="(d, j) in v.detail" :key="j">
<span class="app-text-muted-2">
{{ d.label }}
</span> </span>
<span>{{ d.value || '-' }}</span> </div>
<q-btn
v-if="history"
flat
round
class="app-text-muted-2"
padding="xs"
icon="mdi-history"
size="sm"
@click.stop="$emit('history')"
/>
<q-btn
:key="data.code"
flat
round
padding="xs"
class="app-text-muted-2"
icon="mdi-dots-vertical"
size="sm"
@click.stop
>
<q-menu class="bordered">
<q-list v-close-popup>
<q-item
clickable
dense
class="row q-py-sm"
style="white-space: nowrap"
@click.stop="$emit('enterCard', 'INFO')"
>
<q-icon
name="mdi-eye-outline"
class="col-3"
size="xs"
style="color: hsl(var(--green-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('viewDetail') }}
</span>
</q-item>
<q-item
dense
clickable
class="row q-py-sm"
style="white-space: nowrap"
@click="$emit('updateCard', 'INFO', true)"
v-close-popup
>
<q-icon
name="mdi-pencil-outline"
class="col-3"
size="xs"
style="color: hsl(var(--cyan-6-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('edit') }}
</span>
</q-item>
<q-item
dense
clickable
@click="$emit('deleteCard')"
v-close-popup
>
<q-icon
name="mdi-trash-can-outline"
size="xs"
class="col-3 app-text-negative"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('delete') }}
</span>
</q-item>
<q-item dense>
<q-item-section class="q-py-sm">
<div class="q-pa-sm surface-2 rounded">
<q-toggle
dense
size="sm"
@click="$emit('toggleStatus', disabled === false)"
:model-value="!disabled"
val="xs"
padding="none"
>
<div class="q-ml-xs">
{{
!disabled ? $t('switchOnLabel') : $t('switchOffLabel')
}}
</div>
</q-toggle>
</div>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<!-- profile -->
<AppCircle
bordered
class="avatar"
style="border: 2px solid var(--border-color); overflow: visible"
@click="$emit('editProfile')"
>
<q-img
:src="data.img ?? '/no-profile.png'"
style="
object-fit: cover;
width: 100%;
height: 100%;
border-radius: 50%;
"
>
<template #error>
<div
style="background: none"
class="full-width full-height items-center justify-center flex"
>
<q-img src="/no-profile.png" width="5rem" />
</div>
</template>
</q-img>
<div class="avatar__status"></div>
</AppCircle>
<!-- name symbol -->
<span class="items-center row">
{{ data.name }}
<Icon
v-if="data.male || data.female"
class="q-pl-xs"
:class="{
'symbol-gender': data.male || data.female,
'symbol-gender__male': !disabled && data.male,
'symbol-gender__female': !disabled && data.female,
'symbol-gender__disable': disabled,
}"
:icon="`material-symbols:${data.male ? 'male' : 'female'}`"
width="24px"
/>
</span>
<span style="color: hsl(var(--text-mute)); scale: 0.9">
{{ data.code || '-' }}
</span>
</div>
<!-- detail -->
<q-separator v-if="data.detail" class="q-my-sm q-mx-md" />
<div class="row" v-for="v in data.detail" :key="v.value" v-if="data.detail">
<div class="column items-center" style="width: min-content">
<div class="detail-icon">
<q-icon size="md" style="scale: 0.5" :name="v.icon" />
</div> </div>
</div> </div>
</AppBox> <div class="col row items-center full-width">
</div> <span class="ellipsis">{{ v.value || '-' }}</span>
<q-tooltip
anchor="bottom left"
self="bottom left"
:offset="[10, 20]"
:delay="500"
>
{{ v.value || '-' }}
</q-tooltip>
</div>
</div>
</AppBox>
</template> </template>
<style scoped> <style scoped>
.person-box { .person-box {
background-color: var(--surface-2); background-color: var(--surface-1);
border-radius: var(--radius-2) !important;
transition: 100ms ease-in-out; transition: 100ms ease-in-out;
padding: 0; padding: var(--size-2);
&.person-box__disabled { &.person-box__disabled {
opacity: 0.4; opacity: 0.5;
filter: grayscale(0.9);
.status-circle { .status-circle {
background-color: var(--surface-1); background-color: var(--surface-1);
} }
.locale {
filter: grayscale();
}
} }
&.person-box__no-detail { &.person-box__no-detail {
@ -284,6 +251,13 @@ defineEmits<{
background-color: transparent; background-color: transparent;
} }
& .detail-icon {
color: hsl(var(--text-mute-2));
background-color: hsla(var(--stone-6-hsl) / 0.15);
border-radius: 50%;
scale: 0.8;
}
& .bg-gender { & .bg-gender {
color: hsla(var(--_fg)); color: hsla(var(--_fg));
background-color: hsl(var(--_bg)); background-color: hsl(var(--_bg));
@ -297,16 +271,6 @@ defineEmits<{
color: unset; color: unset;
background-color: hsla(var(--_bg) / 0.1); background-color: hsla(var(--_bg) / 0.1);
} }
&.bg-gender__male {
--_fg: 0 100 100%;
--_bg: var(--gender-male);
}
&.bg-gender__female {
--_fg: 0 100 100%;
--_bg: var(--gender-female);
}
} }
& .symbol-gender { & .symbol-gender {
@ -325,23 +289,6 @@ defineEmits<{
} }
} }
& .person-detail {
display: grid;
flex-grow: 1;
grid-template-columns: repeat(2, 1fr);
gap: var(--size-2);
overflow-x: scroll;
& > * {
display: flex;
flex-direction: column;
& > :first-child {
font-size: var(--font-size-0);
}
}
}
& .status-circle { & .status-circle {
width: 18px; width: 18px;
height: 18px; height: 18px;
@ -360,11 +307,6 @@ defineEmits<{
--_hover: hsl(var(--gender-male)); --_hover: hsl(var(--gender-male));
cursor: pointer; cursor: pointer;
box-shadow: var(--shadow-2); box-shadow: var(--shadow-2);
& .person-detail {
--_hover: hsl(var(--_bg));
box-shadow: inset 0em -5px hsl(var(--_bg));
}
} }
} }
@ -374,17 +316,25 @@ defineEmits<{
} }
.avatar { .avatar {
block-size: 7rem; block-size: 5rem;
} transform: rotate(45deg);
.badge { & .avatar__status {
display: inline-block; content: ' ';
border-radius: var(--radius-6); display: block;
background-color: var(--surface-2); block-size: 1rem;
text-wrap: nowrap; aspect-ratio: 1;
position: absolute;
border-radius: 50%;
right: -0.5rem;
border: 1px solid var(--border-color);
top: calc(50% - 0.5rem);
bottom: calc(50% - 0.5rem);
background-color: hsla(var(--positive-bg) / 1);
}
&.empty { & :deep(.q-img) {
background-color: var(--surface-tab); transform: rotate(-45deg);
} }
} }
@ -396,4 +346,60 @@ defineEmits<{
opacity: 80%; opacity: 80%;
} }
} }
.tags {
display: inline-block;
color: hsla(var(--_color) / 1);
background: hsla(var(--_color) / 0.15);
border-radius: var(--radius-2);
padding-inline: var(--size-2);
}
.tags__purple {
--_color: var(--violet-11-hsl);
}
.tags__pink {
--_color: var(--pink-6-hsl);
}
.tags__green {
--_color: var(--teal-10-hsl);
}
.tags__cyan {
--_color: var(--cyan-7-hsl);
}
.tags__red {
--_color: var(--red-6-hsl);
}
.tags__yellow {
--_color: var(--orange-4-hsl);
}
.tags__orange {
--_color: var(--orange-5-hsl);
}
.tags__magenta {
--_color: var(--pink-8-hsl);
}
.dark .tags__purple {
--_color: var(--violet-10-hsl);
}
.dark .tags__green {
--_color: var(--teal-8-hsl);
}
.dark .tags__orange {
--_color: var(--orange-6-hsl);
}
.dark .tags__magenta {
--_color: var(--pink-7-hsl);
}
</style> </style>

View file

@ -9,9 +9,9 @@ html {
--brand-1: #035aa1; --brand-1: #035aa1;
--brand-2: #f50000; --brand-2: #f50000;
--border-color: var(--gray-4); --border-color: var(--gray-2);
--foreground: black; --foreground: black;
--background: var(--gray-1); --background: var(--gray-2);
--surface-0: var(--background); --surface-0: var(--background);
--surface-1: white; --surface-1: white;
--surface-2: var(--gray-0); --surface-2: var(--gray-0);
@ -46,15 +46,15 @@ html {
} }
:where(.dark, .body--dark) { :where(.dark, .body--dark) {
--brand-1: var(--blue-5); --brand-1: #035aa1;
--brand-2: #f50000; --brand-2: #f50000;
--border-color: var(--gray-7); --border-color: var(--gray-7);
--foreground: white; --foreground: white;
--background: var(--gray-10); --background: var(--gray-11);
--surface-0: var(--background); --surface-0: var(--background);
--surface-1: var(--gray-11); --surface-1: var(--gray-10);
--surface-2: var(--gray-10); --surface-2: var(--gray-9);
--surface-3: var(--gray-9); --surface-3: var(--gray-11);
--surface-tab: var(--gray-9); --surface-tab: var(--gray-9);

View file

@ -6,8 +6,8 @@ $primary: var(--brand-1);
$secondary: var(--brand-2); $secondary: var(--brand-2);
$accent: #9c27b0; $accent: #9c27b0;
$dark: var(--gray-11); $dark: var(--gray-10);
$dark-page: var(--gray-10); $dark-page: var(--gray-11);
$positive: #00bd9d; $positive: #00bd9d;
$negative: var(--red-9); $negative: var(--red-9);
@ -24,6 +24,7 @@ $separator-dark-color: var(--border-color);
} }
.q-field__control { .q-field__control {
background: var(--surface-1);
color: $primary; color: $primary;
} }

View file

@ -2,6 +2,7 @@ export default {
branchManagementCaption: 'Manage All Branch', branchManagementCaption: 'Manage All Branch',
branchInHQ: 'Branch within the main office', branchInHQ: 'Branch within the main office',
office: 'Office',
branchLabel: 'Branch', branchLabel: 'Branch',
branchHQLabel: 'Headquarters', branchHQLabel: 'Headquarters',
branchName: 'Branch Name', branchName: 'Branch Name',
@ -20,4 +21,6 @@ export default {
branchLabelStatus: 'Branch Status', branchLabelStatus: 'Branch Status',
registeredBranch: 'Registered Branch', registeredBranch: 'Registered Branch',
branchStatTitle: 'Summary data of branch',
}; };

View file

@ -8,6 +8,9 @@ export default {
customerEmployeeTooltipCaption: 'Click + to add an employee', customerEmployeeTooltipCaption: 'Click + to add an employee',
customerEmployerAdd: 'Add employer', customerEmployerAdd: 'Add employer',
customerEmployeeAdd: 'Add employee', customerEmployeeAdd: 'Add employee',
nameEmployee:'Name Employee',
EMPLOYER: 'Employer', EMPLOYER: 'Employer',
EMPLOYEE: 'Employee', EMPLOYEE: 'Employee',
customerEmployerStatTitle: 'Employer data summary', customerEmployerStatTitle: 'Employer data summary',
@ -53,4 +56,7 @@ export default {
editBy: 'Edit by', editBy: 'Edit by',
valueBefore: 'Before', valueBefore: 'Before',
valueAfter: 'After', valueAfter: 'After',
serviceWorkTotal: 'Service Work Total',
priceInformation: 'Price Information',
}; };

View file

@ -6,4 +6,6 @@ export default {
corporationEnglish: 'English company name', corporationEnglish: 'English company name',
companyOwnerName: 'Company Owner Name', companyOwnerName: 'Company Owner Name',
corporation: 'Corporation',
}; };

View file

@ -92,6 +92,7 @@ export default {
formDialogInputNationality: 'Nationality', formDialogInputNationality: 'Nationality',
formDialogTitlePersonnelAddress: 'Personnel Address', formDialogTitlePersonnelAddress: 'Personnel Address',
formDialogTitleEmployerAddress: 'Employer Address',
formDialogTitleAddressPure: 'Address', formDialogTitleAddressPure: 'Address',
formDialogTitleAddressPureEN: 'Address in English', formDialogTitleAddressPureEN: 'Address in English',

View file

@ -39,6 +39,8 @@ export default {
displayField: 'Display Fields', displayField: 'Display Fields',
deleteConfirmTitle: 'Comfirm Deletion', deleteConfirmTitle: 'Comfirm Deletion',
deleteConfirmMessage: 'Do you want to delete this item?', deleteConfirmMessage: 'Do you want to delete this item?',
headquartersNotEstablished: 'You have not yet established the headquarters. You need to establish the headquarters before you can create personnel.',
changePassword: 'Change Password', changePassword: 'Change Password',
signature: 'Signature', signature: 'Signature',
addSignature: 'Add Signature', addSignature: 'Add Signature',
@ -59,6 +61,9 @@ export default {
baseOnDevice: 'Base on Device', baseOnDevice: 'Base on Device',
person: 'Person', person: 'Person',
recordsPage: 'Showing {resultcurrentPage} out of {total} records', recordsPage: 'Showing {resultcurrentPage} out of {total} records',
showing: 'Showing',
dataSum: 'Data Summaries',
createdAt: 'Created At',
...status, ...status,
...main, ...main,
...address, ...address,

75
src/i18n/en-US/object.ts Normal file
View file

@ -0,0 +1,75 @@
export default {
branchManagement: {
label: {
branchManagementCaption: 'Manage All Branch',
branchLabel: 'Branch',
branchHQLabel: 'Headquarters',
},
table: {
title: {
office: 'Office',
ddress: 'Address',
// formDialogInputTelephone: 'Telephone Number',
telephone: 'Telephone Number',
type: 'Type',
},
},
input: {
label: {
search: 'Search',
},
},
manu: {
branchName: 'Branch Name',
branchLabelTel: 'Branch Telephone No',
branchLabelAddress: 'Branch Address',
branchLabelType: 'Branch Type',
viewDetail: 'View Detail',
edit: 'Edit',
delete: 'Delete',
switchOnLabel: 'Open',
switchOffLabel: 'Close',
},
},
formDialog: {
section: {
formDialogTitleInformation: 'Basic Information',
formDialogTitleContact: 'Contact Information',
formDialogTitleAddress: ' Head office address information',
formDialogTitleLocation: 'Head Office Location',
formDialogTitleImg: 'Office Image',
subtopic: {
formDialogAddress: 'Address in Thai',
formDialogAddressEN: 'Address in English',
},
},
input: {
formDialogInputCode: 'Head Office Code',
formDialogInputBrId: 'Branch Code',
formDialogInputTaxNo: 'Tax Identification Number',
formDialogInputNameSubBranch: 'Branch Name',
formDialogInputNameSubBranchEn: 'Branch Name (English)',
formDialogInputEmailSubBranch: 'Branch Contact Email',
formDialogInputTelephoneSubBranch: 'Branch Telephone Number',
formDialogInputContactName: 'Contact',
formDialogInputTelephoneContact: 'Contact Telephone Number',
address: 'Address',
province: 'Province',
district: 'District',
subDistrict: 'Sub-district',
zipCode: 'Zip Code',
},
button: {
formDialogUploadQrCode: 'Upload QR Code',
formDialogBtnLocation: 'Start sharing location',
formDialogBtnImg: 'Add Office Image',
agree: 'Ok',
cancel: 'Cancel',
},
},
};

View file

@ -12,7 +12,7 @@ export default {
DELEGATE: 'Delegate', DELEGATE: 'Delegate',
AGENCY: 'Agency', AGENCY: 'Agency',
personnelStatTitle: 'Summary data of ', personnelStatTitle: 'Summary data',
personnelCardUserType: 'Type', personnelCardUserType: 'Type',
personnelCardTelephone: 'Telephone', personnelCardTelephone: 'Telephone',

View file

@ -9,6 +9,8 @@ export default {
service: 'Service', service: 'Service',
product: 'Product', product: 'Product',
productGroup: 'Product Group: {name}',
productType: 'Product Type: {name}',
messageTooltipNoProduct: 'No Product and Service Groups', messageTooltipNoProduct: 'No Product and Service Groups',
messageTooltipProductCreate: 'Click + for product and service groups.', messageTooltipProductCreate: 'Click + for product and service groups.',
@ -19,8 +21,9 @@ export default {
productCode: 'Product Code', productCode: 'Product Code',
productName: 'Product Name', productName: 'Product Name',
processingTime: 'Processing Time (Day)', productDetail: 'Product Detail',
priceeInformation: 'Price Information', productProcessingTime: 'Processing Time (Day)',
priceInformation: 'Price Information',
salePrice: 'Sale Price', salePrice: 'Sale Price',
agentPrice: 'Agent Price', agentPrice: 'Agent Price',
processingPrice: 'Processing Price', processingPrice: 'Processing Price',

View file

@ -2,6 +2,7 @@ export default {
branchManagementCaption: 'จัดการสาขาทั้งหมด', branchManagementCaption: 'จัดการสาขาทั้งหมด',
branchInHQ: 'สาขาภายในสำนักงานใหญ่', branchInHQ: 'สาขาภายในสำนักงานใหญ่',
office: 'สำนักงาน',
branchLabel: 'สาขา', branchLabel: 'สาขา',
branchHQLabel: 'สำนักงานใหญ่', branchHQLabel: 'สำนักงานใหญ่',
branchName: 'ชื่อสาขา', branchName: 'ชื่อสาขา',
@ -20,4 +21,6 @@ export default {
branchLabelStatus: 'สถานะสาขา', branchLabelStatus: 'สถานะสาขา',
registeredBranch: 'สาขาที่ลงทะเบียน', registeredBranch: 'สาขาที่ลงทะเบียน',
branchStatTitle: 'สรุปจัดการสาขา',
}; };

View file

@ -8,6 +8,9 @@ export default {
customerEmployeeTooltipCaption: 'คลิก + เพื่อเพิ่มลูกจ้าง', customerEmployeeTooltipCaption: 'คลิก + เพื่อเพิ่มลูกจ้าง',
customerEmployerAdd: 'เพิ่มนายจ้าง', customerEmployerAdd: 'เพิ่มนายจ้าง',
customerEmployeeAdd: 'เพิ่มลูกจ้าง', customerEmployeeAdd: 'เพิ่มลูกจ้าง',
nameEmployee:'ชื่อลูกจ้าง',
EMPLOYER: 'นายจ้าง', EMPLOYER: 'นายจ้าง',
EMPLOYEE: 'ลูกจ้าง', EMPLOYEE: 'ลูกจ้าง',
customerEmployerStatTitle: 'สรุปจำนวนข้อมูลนายจ้าง', customerEmployerStatTitle: 'สรุปจำนวนข้อมูลนายจ้าง',
@ -61,4 +64,7 @@ export default {
editBy: 'แก้ไขโดย', editBy: 'แก้ไขโดย',
valueBefore: 'แก้ไขใหม่', valueBefore: 'แก้ไขใหม่',
valueAfter: 'ค่าเดิม', valueAfter: 'ค่าเดิม',
serviceWorkTotal: 'จำนวนงานทั้งหมด',
priceInformation: 'ข้อมูลราคา',
}; };

View file

@ -6,4 +6,6 @@ export default {
corporationEnglish: 'ชื่อ บริษัท ภาษาอังกฤษ', corporationEnglish: 'ชื่อ บริษัท ภาษาอังกฤษ',
companyOwnerName: 'ชื่อเจ้าของบริษัท', companyOwnerName: 'ชื่อเจ้าของบริษัท',
corporation: 'ชื่อ บริษัท/นิติบุคคล',
}; };

View file

@ -90,6 +90,7 @@ export default {
formDialogInputNationality: 'สัญชาติ', formDialogInputNationality: 'สัญชาติ',
formDialogTitlePersonnelAddress: 'ข้อมูลที่อยู่พนักงาน', formDialogTitlePersonnelAddress: 'ข้อมูลที่อยู่พนักงาน',
formDialogTitleEmployerAddress: 'ข้อมูลที่อยู่นายจ้าง',
formDialogTitleAddressPure: 'ที่อยู่', formDialogTitleAddressPure: 'ที่อยู่',
formDialogTitleAddressPureEN: 'ที่อยู่ภาษาอังกฤษ', formDialogTitleAddressPureEN: 'ที่อยู่ภาษาอังกฤษ',

View file

@ -39,6 +39,7 @@ export default {
displayField: 'ข้อมูลที่แสดง', displayField: 'ข้อมูลที่แสดง',
deleteConfirmTitle: 'ยืนยันการลบข้อมูล', deleteConfirmTitle: 'ยืนยันการลบข้อมูล',
deleteConfirmMessage: 'คุณต้องการลบข้อมูลใช่หรือไม่', deleteConfirmMessage: 'คุณต้องการลบข้อมูลใช่หรือไม่',
headquartersNotEstablished: 'ท่านยังไม่ได้สร้างสำนักงานใหญ่ ต้องสร้างสำนักงานใหญ่ก่อนจึงจะสร้าง บุคลากรได้',
changePassword: 'เปลี่ยนรหัสผ่าน', changePassword: 'เปลี่ยนรหัสผ่าน',
signature: 'ลายเซ็น', signature: 'ลายเซ็น',
addSignature: 'เพิ่มลายเซ็น', addSignature: 'เพิ่มลายเซ็น',
@ -59,6 +60,9 @@ export default {
baseOnDevice: 'สีตามอุปกรณ์', baseOnDevice: 'สีตามอุปกรณ์',
person: 'คน', person: 'คน',
recordsPage: 'แสดง {resultcurrentPage} รายการจาก {total} รายการ', recordsPage: 'แสดง {resultcurrentPage} รายการจาก {total} รายการ',
showing: 'แสดงทีละ',
dataSum: 'สรุปจำนวนข้อมูล',
createdAt: 'สร้างเมื่อ',
...status, ...status,
...main, ...main,
...address, ...address,

View file

@ -9,7 +9,7 @@ export default {
mainCustomerCaption: 'จัดการคนภายในองค์กร', mainCustomerCaption: 'จัดการคนภายในองค์กร',
mainProductTitle: 'สินค้าและบริการ', mainProductTitle: 'สินค้าและบริการ',
mainProductCaption: 'รายการสินค้าและบริการ', mainProductCaption: 'ประเภทสินค้าและบริการ',
mainQuotationTitle: 'ใบเสนอราคา', mainQuotationTitle: 'ใบเสนอราคา',
mainQuotationCaption: 'รายการใบเสนอราคา', mainQuotationCaption: 'รายการใบเสนอราคา',

75
src/i18n/th-th/object.ts Normal file
View file

@ -0,0 +1,75 @@
export default {
branchManagement: {
label: {
branchManagementCaption: 'จัดการทุกสาขา',
branchLabel: 'สาขา',
branchHQLabel: 'สำนักงานใหญ่',
},
table: {
title: {
office: 'สำนักงาน',
address: 'ที่อยู่',
// formDialogInputTelephone: 'เบอร์โทรศัพท์',
telephone: 'เบอร์โทรศัพท์',
type: 'ประเภท',
},
},
input: {
label: {
search: 'ค้นหา',
},
},
manu: {
branchName: 'ชื่อสาขา',
branchLabelTel: 'เบอร์โทรศัพท์สาขา',
branchLabelAddress: 'ที่อยู่สาขา',
branchLabelType: 'ประเภทสาขา',
viewDetail: 'ดูรายละเอียด',
edit: 'แก้ไข',
delete: 'ลบ',
switchOnLabel: 'เปิด',
switchOffLabel: 'ปิด',
},
},
formDialog: {
section: {
formDialogTitleInformation: 'ข้อมูลพื้นฐาน',
formDialogTitleContact: 'ข้อมูลติดต่อ',
formDialogTitleAddress: 'ข้อมูลที่อยู่สำนักงานใหญ่',
formDialogTitleLocation: 'ตำแหน่งที่ตั้งสำนักงานใหญ่',
formDialogTitleImg: 'ภาพสำนักงาน',
subtopic: {
formDialogAddress: 'ที่อยู่ภาษาไทย',
formDialogAddressEN: 'ที่อยู่ภาษาอังกฤษ',
},
},
input: {
formDialogInputCode: 'รหัสสำนักงานใหญ่',
formDialogInputBrId: 'รหัสสาขา',
formDialogInputTaxNo: 'เลขประจำตัวผู้เสียภาษี',
formDialogInputNameSubBranch: 'ชื่อสาขา',
formDialogInputNameSubBranchEn: 'ชื่อสาขา (ภาษาอังกฤษ)',
formDialogInputEmailSubBranch: 'อีเมลติดต่อสาขา',
formDialogInputTelephoneSubBranch: 'เบอร์โทรศัพท์สาขา',
formDialogInputContactName: 'ชื่อผู้ติดต่อ',
formDialogInputTelephoneContact: 'เบอร์โทรศัพท์ผู้ติดต่อ',
address: 'ที่อยู่',
province: 'จังหวัด',
district: 'อำเภอ',
subDistrict: 'ตำบล',
zipCode: 'รหัสไปรษณีย์',
},
button: {
formDialogUploadQrCode: 'อัปโหลด QR Code',
formDialogBtnLocation: 'เริ่มแชร์ตำแหน่งที่ตั้ง',
formDialogBtnImg: 'เพิ่มภาพสำนักงาน',
agree: 'ตกลง',
cancel: 'ยกเลิก',
},
},
};

View file

@ -21,7 +21,8 @@ export default {
productCode: 'รหัสสินค้า', productCode: 'รหัสสินค้า',
productName: 'ชื่อสินค้า', productName: 'ชื่อสินค้า',
processingTime: 'ระยะเวลาดำเนินการ (วัน)', productDetail: 'รายละเอียดสินค้า',
productProcessingTime: 'ระยะเวลาดำเนินการ (วัน)',
priceeInformation: 'ข้อมูลราคา', priceeInformation: 'ข้อมูลราคา',
salePrice: 'ราคาขาย', salePrice: 'ราคาขาย',
agentPrice: 'ราคาตัวแทน', agentPrice: 'ราคาตัวแทน',

View file

@ -16,6 +16,10 @@ import useOptionStore from 'src/stores/options';
import { dialog } from 'src/stores/utils'; import { dialog } from 'src/stores/utils';
import { setLocale } from 'src/utils/datetime'; import { setLocale } from 'src/utils/datetime';
import useUtilsStore from 'src/stores/utils'; import useUtilsStore from 'src/stores/utils';
import useMyBranchStore from 'src/stores/my-branch';
const useMyBranch = useMyBranchStore();
const { fetchListMyBranch } = useMyBranch;
interface NotificationButton { interface NotificationButton {
item: string; item: string;
@ -41,7 +45,10 @@ const userStore = useUserStore();
const rawOption = ref(); const rawOption = ref();
const canvasModal = ref(false); const canvasModal = ref(false);
const leftDrawerOpen = ref(true); const leftDrawerOpen = ref(true);
const leftDrawerMini = ref(false);
const filterUnread = ref(false); const filterUnread = ref(false);
const unread = ref<number>(1); const unread = ref<number>(1);
// const filterRole = ref<string[]>(); // const filterRole = ref<string[]>();
@ -135,6 +142,8 @@ watch(
); );
onMounted(async () => { onMounted(async () => {
await fetchListMyBranch(getUserId() ?? '');
const getCurLang = localStorage.getItem('currentLanguage'); const getCurLang = localStorage.getItem('currentLanguage');
if (getCurLang) currentLanguage.value = getCurLang; if (getCurLang) currentLanguage.value = getCurLang;
if (currentLanguage.value === 'English') { if (currentLanguage.value === 'English') {
@ -169,22 +178,25 @@ onMounted(async () => {
<template> <template>
<q-layout view="lHh Lpr lFf"> <q-layout view="lHh Lpr lFf">
<drawer-component v-model:leftDrawerOpen="leftDrawerOpen" /> <drawer-component
:mini="leftDrawerMini"
v-model:left-drawer-open="leftDrawerOpen"
/>
<q-page-container class="surface-1 q-pa-md"> <q-page-container>
<!-- drawer control --> <!-- drawer control -->
<div <div style="position: relative; z-index: 1000" :hidden="$q.screen.lt.sm">
style="position: relative" <div
:style="$q.screen.gt.xs ? 'z-index: 1000' : 'z-index: 10'"
>
<q-avatar
size="36px" size="36px"
style=" style="
position: absolute; position: absolute;
border-radius: 50%;
top: 28px; top: 28px;
left: -18px; left: -22px;
background-color: var(--surface-1); background-color: var(--surface-1);
border: 4px solid var(--surface-1);
" "
class="flex items-center justify-center"
> >
<q-btn <q-btn
flat flat
@ -197,30 +209,47 @@ onMounted(async () => {
background-color: hsl(var(--negative-bg) / 0.1); background-color: hsl(var(--negative-bg) / 0.1);
overflow: hidden; overflow: hidden;
" "
@click="leftDrawerOpen = !leftDrawerOpen" @click="leftDrawerMini = !leftDrawerMini"
> >
<q-icon <q-icon
:name="leftDrawerOpen ? 'mdi-backburger' : 'mdi-forwardburger'" :name="!leftDrawerMini ? 'mdi-backburger' : 'mdi-forwardburger'"
size="16px" size="16px"
style="color: hsl(var(--negative-bg))" style="color: hsl(var(--negative-bg))"
/> />
</q-btn> </q-btn>
</q-avatar> </div>
</div> </div>
<div <div
class="surface-3 bordered rounded q-pb-lg scroll column" class="surface-0 scroll column"
style="height: calc(100vh - 32px); flex-wrap: nowrap" style="height: 100vh; flex-wrap: nowrap; padding-bottom: var(--size-4)"
> >
<!-- header --> <!-- header -->
<div <div
class="q-px-lg row items-center justify-between q-pb-md surface-3 q-pt-lg" class="q-px-lg surface-0 row items-center justify-start q-pb-md q-pt-lg"
style="position: sticky; top: 0; z-index: 8" style="position: sticky; top: 0; z-index: 8"
> >
<q-btn
v-if="$q.screen.lt.sm"
icon="mdi-menu"
flat
dense
rounded
class="q-mr-md"
@click="
() => {
leftDrawerMini = false;
leftDrawerOpen = !leftDrawerOpen;
}
"
/>
<div class="column"> <div class="column">
<span <span
class="title-gradient text-weight-bold" class="title-gradient text-weight-bold"
:class="{ 'text-h6': $q.screen.gt.xs }" :class="{ 'text-h6': $q.screen.gt.xs }"
:style="{
filter: `brightness(${$q.dark.isActive ? '2' : '1'})`,
}"
> >
{{ {{
utilsStore.currentTitle?.title utilsStore.currentTitle?.title
@ -230,16 +259,29 @@ onMounted(async () => {
: 'Jobs Worker Service' : 'Jobs Worker Service'
}} }}
</span> </span>
<span class="text-caption"> <div class="flex items-center" style="gap: var(--size-1)">
{{ <template v-for="(item, i) in utilsStore.currentTitle.path">
utilsStore.currentTitle?.caption <span
? $t(utilsStore.currentTitle?.caption) class="text-caption cursor-pointer"
: '' @click="item.handler?.()"
}} >
</span> {{
item.text
? $t(item.text, {
...(item.argsi18n || {}),
})
: ''
}}
</span>
<q-icon
name="mdi-chevron-right"
v-if="i + 1 !== utilsStore.currentTitle.path.length"
/>
</template>
</div>
</div> </div>
<div class="row q-gutter-x-md items-center"> <div class="row q-gutter-x-md items-center" style="margin-left: auto">
<!-- notification --> <!-- notification -->
<q-btn <q-btn
round round

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -99,7 +99,7 @@ const menu = [
onMounted(() => { onMounted(() => {
utilsStore.currentTitle.title = ''; utilsStore.currentTitle.title = '';
utilsStore.currentTitle.caption = ''; utilsStore.currentTitle.path = [{ text: '' }];
}); });
</script> </script>

View file

@ -33,6 +33,9 @@ export type Branch = {
telephoneNo: string; telephoneNo: string;
lineId: string; lineId: string;
contact: BranchContact[]; contact: BranchContact[];
_count:{
branch:number
}
}; };
export type BranchWithChildren = Branch & { branch: Branch[] }; export type BranchWithChildren = Branch & { branch: Branch[] };
@ -64,6 +67,6 @@ export type BranchUserStats = {
id: string; id: string;
nameEN: string; nameEN: string;
name: string; name: string;
count: 0; count: number;
isHeadOffice: boolean; isHeadOffice: boolean;
}; };

View file

@ -336,11 +336,13 @@ const useCustomerStore = defineStore('api-customer', () => {
transactionId?: string; transactionId?: string;
}, },
) { ) {
const { code, branchNo, ...playload } = data;
const res = await api.put< const res = await api.put<
Customer & { Customer & {
branch: CustomerBranch[]; branch: CustomerBranch[];
} }
>(`/customer-branch/${id}`, data, { >(`/customer-branch/${id}`, playload, {
headers: { headers: {
'X-Session-Id': flow?.sessionId, 'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid, 'X-Rtid': flow?.refTransactionId || flowStore.rtid,

View file

@ -4,6 +4,7 @@ import { Status } from '../types';
export type CustomerType = 'CORP' | 'PERS'; export type CustomerType = 'CORP' | 'PERS';
export type Customer = { export type Customer = {
registeredBranchId: string;
imageUrl: string; imageUrl: string;
id: string; id: string;
code: string; code: string;
@ -63,7 +64,7 @@ export type CustomerBranch = {
export type CustomerBranchCreate = { export type CustomerBranchCreate = {
code?: string; code?: string;
branchNo: number; branchNo?: number;
address: string; address: string;
addressEN: string; addressEN: string;
provinceId?: string | null; provinceId?: string | null;

View file

@ -71,7 +71,7 @@ const useEmployeeStore = defineStore('api-employee', () => {
transactionId?: string; transactionId?: string;
}, },
) { ) {
const { image, ...payload } = data; const { code, image, ...payload } = data;
const res = await api.post< const res = await api.post<
Employee & { profileImageUrl: string; profileImageUploadUrl: string } Employee & { profileImageUrl: string; profileImageUploadUrl: string }
>('/employee', payload, { >('/employee', payload, {
@ -104,7 +104,7 @@ const useEmployeeStore = defineStore('api-employee', () => {
transactionId?: string; transactionId?: string;
}, },
) { ) {
const { image, ...payload } = data; const { code, image, ...payload } = data;
const res = await api.put< const res = await api.put<
Employee & { imageUrl: string; profileImageUploadUrl: string } Employee & { imageUrl: string; profileImageUploadUrl: string }
>(`/employee/${id}`, payload, { >(`/employee/${id}`, payload, {

View file

@ -2,6 +2,8 @@ import { District, Province, SubDistrict } from '../address';
import { Status } from '../types'; import { Status } from '../types';
import { User } from '../user/types'; import { User } from '../user/types';
import { Customer , CustomerBranch } from '../customer/types';
export type Employee = { export type Employee = {
id: string; id: string;
code: string; code: string;
@ -45,14 +47,16 @@ export type Employee = {
district: District | null; district: District | null;
province: Province | null; province: Province | null;
profileImageUrl: string | null; profileImageUrl: string | null;
customerBranch: CustomerBranch & { customer: Customer } ;
}; };
export type EmployeeCreate = { export type EmployeeCreate = {
code: string;
image: File | null; image: File | null;
customerBranchId: string; customerBranchId: string;
status?: Status; status?: Status;
nrcNo: string; nrcNo: string;
dateOfBirth: Date | null; dateOfBirth: Date | null;

View file

@ -8,6 +8,9 @@ import { Branch } from '../branch/types';
import useFlowStore from '../flow'; import useFlowStore from '../flow';
const useMyBranch = defineStore('useMyBranchStore', () => { const useMyBranch = defineStore('useMyBranchStore', () => {
const myBranch = ref<Branch[]>();
const currentMyBranch = ref<Branch>();
const flowStore = useFlowStore(); const flowStore = useFlowStore();
async function fetchListOptionBranch< async function fetchListOptionBranch<
Options extends { Options extends {
@ -52,8 +55,37 @@ const useMyBranch = defineStore('useMyBranchStore', () => {
return false; return false;
} }
async function fetchListMyBranch(
userId: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get<Pagination<Branch[]>>(`/user/${userId}/branch`, {
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
});
if (!res) return false;
if (res.status === 200) {
myBranch.value = res.data.result;
return res.data;
}
return false;
}
return { return {
myBranch,
currentMyBranch,
fetchListOptionBranch, fetchListOptionBranch,
fetchListMyBranch,
}; };
}); });

View file

@ -23,7 +23,11 @@ import {
import { ref } from 'vue'; import { ref } from 'vue';
import useFlowStore from '../flow'; import useFlowStore from '../flow';
import useMyBranchStore from 'src/stores/my-branch';
const useProductServiceStore = defineStore('api-product-service', () => { const useProductServiceStore = defineStore('api-product-service', () => {
const useMyBranch = useMyBranchStore();
// Product Type // Product Type
const flowStore = useFlowStore(); const flowStore = useFlowStore();
@ -301,6 +305,10 @@ const useProductServiceStore = defineStore('api-product-service', () => {
v !== undefined && params.append(k, v.toString()); v !== undefined && params.append(k, v.toString());
} }
if (useMyBranch.myBranch?.length !== 0 && useMyBranch.myBranch) {
params.append('registeredBranchId', useMyBranch.myBranch[0].id);
}
const query = params.toString(); const query = params.toString();
const res = await api.get<Pagination<ProductList[]>>( const res = await api.get<Pagination<ProductList[]>>(
@ -429,6 +437,10 @@ const useProductServiceStore = defineStore('api-product-service', () => {
v !== undefined && params.append(k, v.toString()); v !== undefined && params.append(k, v.toString());
} }
if (useMyBranch.myBranch?.length !== 0 && useMyBranch.myBranch) {
params.append('registeredBranchId', useMyBranch.myBranch[0].id);
}
const query = params.toString(); const query = params.toString();
const res = await api.get<Pagination<Service[]>>( const res = await api.get<Pagination<Service[]>>(
@ -733,6 +745,10 @@ const useProductServiceStore = defineStore('api-product-service', () => {
v !== undefined && params.append(k, v.toString()); v !== undefined && params.append(k, v.toString());
} }
if (useMyBranch.myBranch?.length !== 0 && useMyBranch.myBranch) {
params.append('registeredBranchId', useMyBranch.myBranch[0].id);
}
const query = params.toString(); const query = params.toString();
const res = await api.get<Pagination<ServiceAndProduct[]>>( const res = await api.get<Pagination<ServiceAndProduct[]>>(

View file

@ -97,6 +97,7 @@ export type UserAttachmentDelete = {
}; };
export type UserTypeStats = { export type UserTypeStats = {
[x: string]: number;
USER: number; USER: number;
MESSENGER: number; MESSENGER: number;
DELEGATE: number; DELEGATE: number;

View file

@ -95,9 +95,21 @@ export function formatNumberDecimal(num: number, point: number): string {
} }
const useUtilsStore = defineStore('utilsStore', () => { const useUtilsStore = defineStore('utilsStore', () => {
const currentTitle = ref<{ title: string; caption: string }>({ const currentTitle = ref<{
title: string;
path: {
text: string;
argsi18n?: Record<string, string>;
handler?: () => unknown;
}[];
}>({
title: '', title: '',
caption: '', path: [
{
text: '',
handler: () => {},
},
],
}); });
return { return {