feat: CRUD ProductType

This commit is contained in:
Net 2024-06-12 15:37:17 +07:00
parent 069dcce9b6
commit a61b3e550e
3 changed files with 450 additions and 242 deletions

View file

@ -1,24 +1,32 @@
<script setup lang="ts">
import { ref } from 'vue';
import AppBox from 'components/app/AppBox.vue';
import AddButton from 'components/AddButton.vue';
import ProductCardComponent from 'src/components/04_product-service/ProductCardComponent.vue';
import StatCard from 'components/StatCardComponent.vue';
import NewProductCardCompoent from 'components/04_product-service/NewProductCardComponent.vue';
import DrawerInfo from 'src/components/DrawerInfo.vue';
import BasicInformation from 'src/components/04_product-service/BasicInformation.vue';
import FormDialog from 'src/components/FormDialog.vue';
import TooltipComponent from 'components/TooltipComponent.vue';
import { dialog } from 'src/stores/utils';
import useProductServiceStore from 'src/stores/product-service';
import {
ProductGroup,
ProductGroupCreate,
ProductGroupUpdate,
Product,
ProductCreate,
ProductUpdate,
} from 'src/stores/product-service/types';
const productServiceStore = useProductServiceStore();
const {
createProductServiceType,
editProductServiceType,
deleteProductServiceType,
fetchListProductServiceType,
fetchStatsProductGroup,
fetchListProductService,
createProductService,
@ -49,12 +57,13 @@ const stat = ref<
{
count: number;
label: string;
color: 'pink' | 'purple' | 'green';
color: 'pink' | 'purple' | 'green' | 'orange';
}[]
>([
{ count: 5, label: 'branchHQLabel', color: 'pink' },
{ count: 4, label: 'branchLabel', color: 'purple' },
{ count: 12, label: 'branchLabel', color: 'green' },
{ count: 0, label: 'productAndService', color: 'pink' },
{ count: 0, label: 'productAndServiceType', color: 'purple' },
{ count: 0, label: 'service', color: 'orange' },
{ count: 0, label: 'service', color: 'green' },
]);
import { useI18n } from 'vue-i18n';
@ -71,11 +80,12 @@ const groupName = ref<string>('งาน MOU');
const dialogProductServiceType = ref<boolean>(false);
const dialogTotalProduct = ref<boolean>(false);
const productMode = ref<'group' | 'type' | 'service' | 'product'>('group');
const productGroup = ref<ProductGroup[]>();
const productGroup = ref<Product[]>();
const productType = ref<Product[]>();
const previousValue = ref();
const formData = ref<ProductGroupCreate>({
const formData = ref<ProductCreate>({
remark: '',
detail: '',
name: '',
@ -83,9 +93,39 @@ const formData = ref<ProductGroupCreate>({
});
const currentId = ref<string>('');
const currentIdType = ref<string>('');
async function fetchListType() {
const res = await fetchListProductServiceType({
productGroupId: currentId.value,
});
if (res) {
productType.value = res;
}
}
async function fetchListGroups() {
const res = await fetchListProductService();
if (res) {
productGroup.value = res;
}
}
async function submitType() {
console.log(formData.value);
if (drawerInfo.value) {
await editProductServiceType(currentIdType.value, {
...formData.value,
productGroupId: currentId.value,
});
drawerInfo.value = false;
} else {
dialogInputForm.value = false;
await createProductServiceType(currentId.value, formData.value);
}
await fetchListType();
}
const itemCard = [
@ -112,12 +152,16 @@ async function deleteProductById() {
action: async () => {
if (productMode.value === 'type') {
// Product Type
await deleteProductServiceType(currentIdType.value);
await fetchListType();
} else {
// Product Group
await deleteProductService(currentId.value);
const res = await deleteProductService(currentId.value);
if (res) {
stat.value[0].count = stat.value[0].count - 1;
}
await fetchListGroups();
}
console.log('deleted');
drawerInfo.value = false;
},
cancel: () => {},
@ -125,8 +169,6 @@ async function deleteProductById() {
}
function undoProductGroup() {
console.log('dasd');
formData.value = {
remark: previousValue.value.remark,
detail: previousValue.value.detail,
@ -136,7 +178,7 @@ function undoProductGroup() {
isEdit.value = false;
}
function assignFormData(data: ProductGroup) {
function assignFormData(data: Product) {
previousValue.value = data;
formData.value = {
@ -162,106 +204,278 @@ async function submitGroup() {
if (drawerInfo.value) {
await editProductService(currentId.value, formData.value);
} else {
await createProductService(formData.value);
const res = await createProductService(formData.value);
if (res) {
stat.value[0].count = stat.value[0].count + 1;
}
}
await fetchListGroups();
clearForm();
}
async function fetchListGroups() {
const res = await fetchListProductService();
if (res) {
productGroup.value = res;
}
}
onMounted(async () => {
const resStatsGroup = await fetchStatsProductGroup();
stat.value[0].count = resStatsGroup ?? 0;
await fetchListGroups();
});
</script>
<template>
<div v class="column q-pb-lg">
<div
v-if="productMode === 'group'"
class="text-h6 text-weight-bold q-mb-md"
></div>
<div v-else-if="productMode === 'type'" class="row items-center q-mb-md">
<q-btn
round
icon="mdi-arrow-left"
flat
dense
@click="productMode = 'group'"
class="q-mr-md"
/>
<div class="text-h6 app-text-muted q-mr-sm hover-underline">
{{ groupName }}
</div>
<div class="text-h6 app-text-muted q-mx-sm">/</div>
<div class="text-h6 text-weight-bold">
{{ $t('ProductAndServiceType') }}
</div>
</div>
<div v-else-if="productMode === 'service'" class="row items-center q-mb-md">
<q-btn
round
icon="mdi-arrow-left"
flat
dense
@click="productMode = 'type'"
class="q-mr-md"
/>
<div class="text-h6 app-text-muted q-mr-sm hover-underline">
{{ groupName }}
</div>
<div class="text-h6 app-text-muted q-mx-sm">/</div>
<div class="text-h6 app-text-muted q-mr-sm hover-underline">
{{ 'บริการพิสูจน์สัญชาติ' }}
</div>
<div class="text-h6 app-text-muted q-mx-sm">/</div>
<div class="text-h6 text-weight-bold">
{{ $t('mainProductTitle') }}
</div>
<div class="column q-pb-lg">
<div class="row text-h6 text-weight-bold q-mb-md">
{{ $t('productAndService') }}
</div>
<AppBox
v-if="productMode === 'group' || productMode === 'type'"
v-if="productGroup?.length === 0"
class="column"
:no-padding="productGroup?.length !== 0"
bordered
class="q-mb-md"
style="min-height: 70vh"
>
<StatCard label-i18n :branch="stat" :dark="$q.dark.isActive" />
<template>
<TooltipComponent
class="self-end"
title="messageTooltipNoProduct"
caption="messageTooltipProductCreate"
imgSrc="personnel-table-"
/>
<div class="row items-center justify-center" style="flex-grow: 1">
<AddButton
label="productCreate"
@trigger="() => (dialogInputForm = true)"
/>
</div>
</template>
</AppBox>
<AppBox
v-if="productMode === 'group' || productMode === 'type'"
bordered
style="min-height: 80vh"
>
<div class="row justify-between">
<div
v-if="productMode === 'type'"
class="text-h6 text-weight-bold q-mb-md"
>
{{ $t('ProductAndServiceType') }}
<div v-if="productGroup?.length !== 0">
<div
v-if="productMode === 'group'"
class="text-h6 text-weight-bold q-mb-md"
></div>
<div v-else-if="productMode === 'type'" class="row items-center q-mb-md">
<q-btn
round
icon="mdi-arrow-left"
flat
dense
@click="productMode = 'group'"
class="q-mr-md"
/>
<div class="text-h6 app-text-muted q-mr-sm hover-underline">
{{ groupName }}
</div>
<div
v-else-if="productMode === 'group'"
class="text-h6 text-weight-bold q-mb-md"
>
{{ $t('ProductAndServiceAll') }}
<div class="text-h6 app-text-muted q-mx-sm">/</div>
<div class="text-h6 text-weight-bold">
{{ $t('productAndServiceType') }}
</div>
</div>
<div
v-else-if="productMode === 'service'"
class="row items-center q-mb-md"
>
<q-btn
round
icon="mdi-arrow-left"
flat
dense
@click="productMode = 'type'"
class="q-mr-md"
/>
<div class="text-h6 app-text-muted q-mr-sm hover-underline">
{{ groupName }}
</div>
<div class="text-h6 app-text-muted q-mx-sm">/</div>
<div class="text-h6 app-text-muted q-mr-sm hover-underline">
{{ 'บริการพิสูจน์สัญชาติ' }}
</div>
<div class="text-h6 app-text-muted q-mx-sm">/</div>
<div class="text-h6 text-weight-bold">
{{ $t('mainProductTitle') }}
</div>
</div>
<AppBox
v-if="productMode === 'group' || productMode === 'type'"
bordered
class="q-mb-md"
>
<StatCard label-i18n :branch="stat" :dark="$q.dark.isActive" />
</AppBox>
<AppBox
v-if="productMode === 'group' || productMode === 'type'"
bordered
style="min-height: 80vh"
>
<div class="row justify-between">
<div
v-if="productMode === 'type'"
class="text-h6 text-weight-bold q-mb-md"
>
{{ $t('productAndServiceType') }}
</div>
<div
v-else-if="productMode === 'group'"
class="text-h6 text-weight-bold q-mb-md"
>
{{ $t('productAndServiceAll') }}
</div>
<div
v-if="productMode === 'group' || productMode === 'type'"
class="row q-px-md q-pb-md"
>
<q-input
style="width: 300px"
outlined
dense
:label="$t('search')"
class="q-mr-lg"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="500"
>
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
<q-btn
icon="mdi-tune-vertical-variant"
size="sm"
:color="$q.dark.isActive ? 'dark' : 'white'"
:text-color="$q.dark.isActive ? 'white' : 'dark'"
class="bordered rounded"
unelevated
>
<q-menu class="bordered">
<q-list v-close-popup dense>
<q-item
clickable
class="flex items-center"
@click="console.log('test')"
>
{{ $t('all') }}
</q-item>
<q-item
clickable
class="flex items-center"
@click="console.log('test')"
>
{{ $t('statusACTIVE') }}
</q-item>
<q-item
clickable
class="flex items-center"
@click="console.log('test')"
>
{{ $t('statusINACTIVE') }}
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
<div class="row q-col-gutter-lg">
<div
:class="`${$q.screen.gt.sm ? 'col-3' : $q.screen.gt.xs ? 'col-6' : 'col-12'}`"
v-for="(v, i) in productMode === 'type'
? productType
: productGroup"
:key="i"
>
<ProductCardComponent
v-if="productMode === 'type'"
:title="v.name"
:subtitle="v.code"
:date="new Date(v.updatedAt)"
:status="v.status"
color="var(--purple-11)"
@view-detail="
() => {
clearForm();
currentIdType = v.id;
assignFormData(v);
isEdit = false;
drawerInfo = true;
}
"
@on-click="
() => {
currentIdType = v.id;
productMode = 'service';
}
"
/>
<ProductCardComponent
v-else-if="productMode === 'group'"
:title="v.name"
:subtitle="v.code"
:date="new Date(v.updatedAt)"
:status="v.status"
color="var(--pink-6)"
@view-detail="
() => {
clearForm();
assignFormData(v);
isEdit = false;
currentId = v.id;
drawerInfo = true;
}
"
@on-click="
async () => {
currentId = v.id;
console.log(v.id);
productMode = 'type';
await fetchListType();
}
"
/>
</div>
<div
:class="`${$q.screen.gt.sm ? 'col-3' : $q.screen.gt.xs ? 'col-6' : 'col-12'}`"
>
<NewProductCardCompoent
v-if="productMode === 'type'"
:label="$t('productAndServiceType')"
:isType="true"
@on-click="
() => {
clearForm();
dialogInputForm = true;
}
"
/>
<NewProductCardCompoent
v-else-if="productMode === 'group'"
:label="$t('productAndService')"
:isType="false"
@on-click="
() => {
clearForm();
dialogInputForm = true;
}
"
/>
</div>
</div>
</AppBox>
<AppBox bordered v-else-if="productMode === 'service'" no-padding>
<div
v-if="productMode === 'group' || productMode === 'type'"
class="row q-px-md q-pb-md"
class="row justify-center q-py-sm"
style="background-color: var(--surface-2)"
>
<q-input
style="width: 300px"
style="width: 500px"
outlined
dense
:label="$t('search')"
class="q-mr-lg"
class="q-mr-md"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="500"
@ -271,160 +485,36 @@ onMounted(async () => {
</template>
</q-input>
<q-btn
icon="mdi-tune-vertical-variant"
size="sm"
icon="mdi-plus"
:color="$q.dark.isActive ? 'dark' : 'white'"
:text-color="$q.dark.isActive ? 'white' : 'dark'"
class="bordered rounded"
class="bordered rounded q-px-sm"
unelevated
>
<q-menu class="bordered">
<q-list v-close-popup dense>
<q-item
clickable
class="flex items-center"
@click="console.log('test')"
>
{{ $t('all') }}
</q-item>
<q-item
clickable
class="flex items-center"
@click="console.log('test')"
>
{{ $t('statusACTIVE') }}
</q-item>
<q-item
clickable
class="flex items-center"
@click="console.log('test')"
>
{{ $t('statusINACTIVE') }}
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
<div class="row q-col-gutter-lg">
<div
:class="`${$q.screen.gt.sm ? 'col-3' : $q.screen.gt.xs ? 'col-6' : 'col-12'}`"
v-for="(v, i) in productMode ? productGroup : productGroup"
:key="i"
>
<ProductCardComponent
v-if="productMode === 'type'"
:title="cardTypeData.title"
:subtitle="cardTypeData.subtitle"
:date="cardTypeData.date"
:status="v.status"
:color="cardTypeData.color"
@view-detail="
() => {
currentId = v.id;
drawerInfo = true;
}
"
@on-click="
() => {
currentId = v.id;
productMode = 'service';
}
"
@click="dialogProductServiceType = true"
/>
<ProductCardComponent
v-else-if="productMode === 'group'"
:title="v.name"
:subtitle="v.code"
:date="new Date(v.updatedAt)"
:status="v.status"
color="var(--pink-6)"
@view-detail="
() => {
assignFormData(v);
currentId = v.id;
drawerInfo = true;
}
"
@on-click="
() => {
currentId = v.id;
productMode = 'type';
}
"
<div class="q-mx-lg">มรวมสนคาทงหมด >></div>
<q-btn
unelevated
@click="dialogTotalProduct = true"
class="bordered rounded q-px-sm"
icon="mdi-plus"
:color="$q.dark.isActive ? 'dark' : 'white'"
:text-color="$q.dark.isActive ? 'white' : 'dark'"
round
/>
</div>
<div
:class="`${$q.screen.gt.sm ? 'col-3' : $q.screen.gt.xs ? 'col-6' : 'col-12'}`"
>
<NewProductCardCompoent
v-if="productMode === 'type'"
:label="$t('ProductAndServiceType')"
:isType="true"
@on-click="() => (dialogInputForm = true)"
/>
<NewProductCardCompoent
v-else-if="productMode === 'group'"
:label="$t('ProductAndService')"
:isType="false"
@on-click="() => (dialogInputForm = true)"
/>
<div class="flex justify-center items-center" style="min-height: 70vh">
asdasdasd
</div>
</div>
</AppBox>
<AppBox bordered v-else-if="productMode === 'service'" no-padding>
<div
class="row justify-center q-py-sm"
style="background-color: var(--surface-2)"
>
<q-input
style="width: 500px"
outlined
dense
:label="$t('search')"
class="q-mr-md"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="500"
>
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
<q-btn
icon="mdi-plus"
:color="$q.dark.isActive ? 'dark' : 'white'"
:text-color="$q.dark.isActive ? 'white' : 'dark'"
class="bordered rounded q-px-sm"
unelevated
@click="dialogProductServiceType = true"
/>
<div class="q-mx-lg">มรวมสนคาทงหมด >></div>
<q-btn
unelevated
@click="dialogTotalProduct = true"
class="bordered rounded q-px-sm"
icon="mdi-plus"
:color="$q.dark.isActive ? 'dark' : 'white'"
:text-color="$q.dark.isActive ? 'white' : 'dark'"
round
/>
</div>
<div class="flex justify-center items-center" style="min-height: 70vh">
asdasdasd
</div>
</AppBox>
</AppBox>
</div>
</div>
<FormDialog
v-model:modal="dialogInputForm"
noAddress
:title="$t('customerEmployerAdd')"
:submit="
() => {
submitGroup();
}
"
:title="$t('addProduct')"
:submit="() => (productMode === 'type' ? submitType() : submitGroup())"
:close="() => {}"
>
<template #information>

View file

@ -1,8 +1,119 @@
import { defineStore } from 'pinia';
import { api } from 'src/boot/axios';
import { ProductGroup, ProductGroupCreate, ProductGroupUpdate } from './types';
import { Product, ProductCreate, ProductUpdate } from './types';
const useProductServiceStore = defineStore('api-product-service', () => {
async function fetchStatsProductType() {
const res = await api.get('/product-type/stats');
if (!res) return false;
if (res.status === 200) {
return res.data;
}
}
async function fetchListProductServiceByIdType(groupId: string) {
const res = await api.get<Product & { productGroupId: string }>(
`/product-type/${groupId}`,
);
if (!res) return false;
if (res.status === 200) {
return res.data;
}
}
async function fetchListProductServiceType(
opts?: { query?: string; productGroupId?: string },
flow?: {
sessionId: string;
refTransactionId: string;
transactionId: string;
},
) {
const params = new URLSearchParams();
for (const [k, v] of Object.entries(opts || {})) {
v !== undefined && params.append(k, v.toString());
}
const query = params.toString();
const res = await api.get<(Product & { productGroupId: string })[]>(
`/product-type${(params && '?'.concat(query)) || ''}`,
{
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId,
'X-Tid': flow?.transactionId,
},
},
);
if (!res) return false;
if (res.status === 200) {
return res.data;
}
return false;
}
async function createProductServiceType(id: string, data: ProductCreate) {
const { code, ...payload } = data;
const res = await api.post<ProductCreate & { productGroupId: string }>(
'/product-type',
{
productGroupId: id,
...payload,
},
);
if (!res) return false;
if (res.status === 200) {
return res.data;
}
return false;
}
async function editProductServiceType(
groupId: string,
data: ProductUpdate & { productGroupId: string },
) {
const { code, ...payload } = data;
const res = await api.put<ProductUpdate & { productGroupId: string }>(
`/product-type/${groupId}`,
{
...payload,
},
);
if (!res) return false;
if (res.status === 200) {
return res.data;
}
return false;
}
async function deleteProductServiceType(groupId: string) {
const res = await api.delete(`/product-type/${groupId}`);
if (!res) return false;
if (res.status === 200) {
return res.data;
}
return false;
}
async function fetchStatsProductGroup() {
const res = await api.get('/product-group/stats');
@ -14,7 +125,7 @@ const useProductServiceStore = defineStore('api-product-service', () => {
}
async function fetchListProductServiceById(groupId: string) {
const res = await api.get<ProductGroup>(`/product-group/${groupId}`);
const res = await api.get<Product>(`/product-group/${groupId}`);
if (!res) return false;
@ -39,7 +150,7 @@ const useProductServiceStore = defineStore('api-product-service', () => {
const query = params.toString();
const res = await api.get<ProductGroup[]>(
const res = await api.get<Product[]>(
`/product-group${(params && '?'.concat(query)) || ''}`,
{
headers: {
@ -58,26 +169,26 @@ const useProductServiceStore = defineStore('api-product-service', () => {
return false;
}
async function createProductService(data: ProductGroupCreate) {
async function createProductService(data: ProductCreate) {
const { code, ...payload } = data;
const res = await api.post<ProductGroupCreate>('/product-group', {
const res = await api.post<ProductCreate>('/product-group', {
...payload,
});
if (!res) return false;
if (res.status === 200) {
if (res.status === 201) {
return res.data;
}
return false;
}
async function editProductService(groupId: string, data: ProductGroupUpdate) {
async function editProductService(groupId: string, data: ProductUpdate) {
const { code, ...payload } = data;
const res = await api.put<ProductGroupUpdate>(`/product-group/${groupId}`, {
const res = await api.put<ProductUpdate>(`/product-group/${groupId}`, {
...payload,
});
@ -103,6 +214,13 @@ const useProductServiceStore = defineStore('api-product-service', () => {
}
return {
fetchStatsProductType,
fetchListProductServiceByIdType,
fetchListProductServiceType,
createProductServiceType,
editProductServiceType,
deleteProductServiceType,
fetchStatsProductGroup,
fetchListProductServiceById,
fetchListProductService,

View file

@ -1,6 +1,6 @@
import { Status } from '../types';
export type ProductGroup = {
export type Product = {
id: string;
code: string;
name: string;
@ -13,14 +13,14 @@ export type ProductGroup = {
updatedAt: string;
};
export interface ProductGroupCreate {
export interface ProductCreate {
remark: string;
detail: string;
name: string;
code: string;
}
export interface ProductGroupUpdate {
export interface ProductUpdate {
remark: string;
detail: string;
name: string;