refactor: add new table

This commit is contained in:
Thanaphon Frappet 2025-01-29 12:59:53 +07:00
parent f409e31cc0
commit 96fcddc817

View file

@ -0,0 +1,380 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { Lang } from 'src/utils/ui';
import { QTableSlots, QTableColumn } from 'quasar';
import { Product, Service } from 'src/stores/product-service/types';
import { calculateAge, dateFormatJS } from 'src/utils/datetime';
import useOptionStore from 'stores/options';
import { formatNumberDecimal, isRoleInclude } from 'src/stores/utils';
import { computed } from 'vue';
const selected = defineModel<unknown[]>('selected');
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const { locale } = useI18n();
const optionStore = useOptionStore();
const priceDisplay = computed(() => ({
price: !isRoleInclude(['sale_agent']),
agentPrice: isRoleInclude([
'admin',
'head_of_admin',
'head_of_sale',
'system',
'owner',
'accountant',
'sale_agent',
]),
serviceCharge: isRoleInclude([
'admin',
'head_of_admin',
'system',
'owner',
'accountant',
]),
}));
defineEmits<{
(e: 'create'): void;
}>();
type ExclusiveProps = {
type: 'service' | 'product';
grid?: boolean;
disabledWorkerId?: string[];
rows: Product[];
};
const columnsProduct = [
{
name: '#check',
align: 'left',
label: '',
field: (_) => '#check',
},
{
name: '#productName',
align: 'left',
label: 'general.name',
field: (v: Product) => v.name,
},
{
name: 'productProcessingTime',
align: 'center',
label: 'productService.product.processingTimeDay',
field: (v: Product) => v.process,
},
{
name: '#priceInformation',
align: 'center',
label: 'productService.product.priceInformation',
field: (v: Product) => v,
},
] satisfies QTableColumn[];
const columnsService = [
{
name: '#check',
align: 'left',
label: '',
field: (_) => '#check',
},
{
name: '#serviceName',
align: 'left',
label: 'general.name',
field: (v: Service) => v.name,
},
{
name: 'serviceDetail',
align: 'left',
label: 'general.detail',
field: (v: Service) => v.detail,
},
{
name: 'serviceWorkTotal',
align: 'left',
label: 'productService.service.totalWork',
field: (v: Service) => v.work.length,
},
{
name: 'createdAt',
align: 'left',
label: 'general.createdAt',
field: (v: Service) => dateFormatJS({ date: v.createdAt }),
},
] satisfies QTableColumn[];
const props = defineProps<ExclusiveProps>();
function getProductImageUrl(
item: (Product | Service) & { type: string; _index: number },
) {
if (item.selectedImage) {
return `${API_BASE_URL}/${item.type}/${item?.id}/image/${item?.selectedImage}`;
}
// NOTE: static image
return `/images/${item.type}-avatar.png`;
}
function handleUpdate() {
if (selected.value?.length === 0) {
selected.value = props.rows?.filter(
(v) => !props.disabledWorkerId?.includes(v.id),
);
} else {
selected.value = [];
}
}
function selectedIndex(item: any) {
return selected.value?.findIndex((v: any) => v.id === item.id);
}
</script>
<template>
<q-table
v-model:selected="selected"
:rows-per-page-options="[0]"
:rows="
rows.map((data, i) => ({
...data,
_index: i,
}))
"
:grid
:columns="type === 'product' ? columnsProduct : columnsService"
hide-bottom
bordered
flat
hide-pagination
selection="multiple"
card-container-class="q-col-gutter-sm"
class="full-width"
row-key="id"
>
<template v-slot:header="props">
<q-tr
style="background-color: hsla(var(--info-bg) / 0.07)"
:props="props"
>
<q-th
v-for="col in type === 'product' ? columnsProduct : columnsService"
:key="col.name"
:props="props"
>
<template v-if="!col.name.startsWith('#')">
{{ $t(col.label) }}
</template>
<template
v-if="
col.name === '#serviceName' ||
col.name === '#productName' ||
col.name === '#priceInformation'
"
>
{{ $t(col.label) }}
</template>
<template v-if="col.name === '#check'">
<q-checkbox
v-model="props.selected"
@update:model-value="(v) => handleUpdate()"
size="sm"
/>
</template>
</q-th>
</q-tr>
</template>
<template
v-slot:body="props: {
row: (Product | Service) & { type: string; _index: number };
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
>
<q-tr
:class="{
dark: $q.dark.isActive,
'selectable-item__disabled': disabledWorkerId?.some(
(id) => id === props.row.id,
),
}"
class="text-center"
>
<q-td
v-for="col in type === 'product' ? columnsProduct : columnsService"
:align="col.align"
:key="col.name"
>
<!-- NOTE: custom column will starts with # -->
<template v-if="!col.name.startsWith('#')">
<span>
{{
typeof col.field === 'string'
? props.row[col.field as keyof (Product | Service)]
: col.field(props.row as any)
}}
</span>
</template>
<template
v-if="col.name === '#productName' || col.name === '#serviceName'"
>
<div class="row full-height">
<q-avatar size="md">
<q-img
:src="getProductImageUrl(props.row)"
:ratio="1"
class="text-center"
/>
</q-avatar>
<p class="column q-ml-sm">
<span class="col-6">
{{ props.row.name }}
</span>
<span class="col-6 app-text-muted">
{{ props.row.code }}
</span>
</p>
</div>
</template>
<template
v-if="
col.name === '#priceInformation' &&
!disabledWorkerId?.some((id) => id === props.row.id)
"
>
<div
class="row full-width q-gutter-x-md no-wrap items-center text-right"
>
<div
class="tags tags-color-orange col column ellipsis-2-lines"
:class="{
disable: props.row.status === 'INACTIVE',
}"
style="min-width: 50px"
v-if="priceDisplay.price"
>
<div class="col app-text-muted-2 text-caption">
{{ $t('productService.product.salePrice') }}
</div>
<div class="col text-weight-bold">
฿{{ formatNumberDecimal(props.row.price || 0, 2) }}
</div>
</div>
<div
class="tags tags-color-purple col column ellipsis-2-lines"
:class="{
disable: props.row.status === 'INACTIVE',
}"
style="min-width: 50px"
v-if="priceDisplay.agentPrice"
>
<div class="col app-text-muted-2 text-caption">
{{ $t('productService.product.agentPrice') }}
</div>
<div class="col text-weight-bold">
฿{{ formatNumberDecimal(props.row.agentPrice || 0, 2) }}
</div>
</div>
<div
class="tags tags-color-pink col column ellipsis-2-lines"
:class="{
disable: props.row.status === 'INACTIVE',
}"
style="min-width: 50px"
v-if="priceDisplay.serviceCharge"
>
<div class="col app-text-muted-2 text-caption">
{{ $t('productService.product.processingPrice') }}
</div>
<div class="col">
฿{{ formatNumberDecimal(props.row.serviceCharge || 0, 2) }}
</div>
</div>
</div>
</template>
<template
v-if="
col.name === '#check' &&
!disabledWorkerId?.some((id) => id === props.row.id)
"
>
<q-checkbox v-model="props.selected" size="sm" />
</template>
</q-td>
</q-tr>
</template>
<template v-slot:item="props: { row: any; rowIndex: number }">
<slot
name="grid"
:item="{
index: props.rowIndex,
...props.row,
_selectedIndex: selectedIndex(props.row),
}"
/>
</template>
</q-table>
</template>
<style scoped>
.selectable-item__disabled {
filter: grayscale(1);
opacity: 0.5;
& :deep(*) {
cursor: not-allowed;
}
}
.tags {
display: inline-block;
color: hsla(var(--_color-tag) / 1);
background: hsla(var(--_color-tag) / 0.075);
border-radius: var(--radius-2);
padding-inline: var(--size-2);
&.disable {
filter: grayscale(100%);
opacity: 80%;
}
}
.tags-color-green {
--_color-tag: var(--teal-10-hsl);
}
.dark .tags-color-green {
--_color-tag: var(--teal-8-hsl);
}
.tags-color-orange {
--_color-tag: var(--orange-5-hsl);
}
.dark .tags-color-orange {
--_color-tag: var(--orange-6-hsl);
}
.tags-color-purple {
--_color-tag: var(--violet-11-hsl);
}
.dark .tags-color-purple {
--_color-tag: var(--violet-10-hsl);
}
.tags-color-pink {
--_color-tag: var(--pink-6-hsl);
}
</style>