feat(specialist-position): implement crud operations for specialist positions
add handler, service, schema and update components for specialist position management update list configuration and form to handle specialist relations
This commit is contained in:
@@ -7,18 +7,18 @@ import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
|
||||
|
||||
// Types
|
||||
import type { DivisionPositionFormData } from '~/schemas/division-position.schema'
|
||||
import type { SpecialistPositionFormData } from '~/schemas/specialist-position.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { genBase } from '~/models/_base'
|
||||
import { genDivisionPosition } from '~/models/division-position'
|
||||
import { genSpecialistPosition } from '~/models/specialist-position'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
divisionId: number
|
||||
specialists: any[]
|
||||
employees: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
@@ -26,21 +26,21 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: DivisionPositionFormData, resetForm: () => void]
|
||||
submit: [values: SpecialistPositionFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: genDivisionPosition() as Partial<DivisionPositionFormData>,
|
||||
initialValues: genSpecialistPosition() as Partial<SpecialistPositionFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [specialist, specialistAttrs] = defineField('specialist_id')
|
||||
const [employee, employeeAttrs] = defineField('employee_id')
|
||||
const [headStatus, headStatusAttrs] = defineField('headStatus')
|
||||
|
||||
@@ -62,6 +62,8 @@ const headStatusStr = computed<string>({
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.specialist_id !== undefined)
|
||||
specialist.value = props.values.specialist_id ? Number(props.values.specialist_id) : null
|
||||
if (props.values.employee_id !== undefined)
|
||||
employee.value = props.values.employee_id ? Number(props.values.employee_id) : null
|
||||
if (props.values.headStatus !== undefined) headStatus.value = !!props.values.headStatus
|
||||
@@ -70,20 +72,18 @@ if (props.values) {
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
specialist.value = null
|
||||
employee.value = null
|
||||
headStatus.value = false
|
||||
}
|
||||
|
||||
// Form submission handler
|
||||
function onSubmitForm() {
|
||||
const formData: DivisionPositionFormData = {
|
||||
const formData: SpecialistPositionFormData = {
|
||||
...genBase(),
|
||||
name: name.value || '',
|
||||
code: code.value || '',
|
||||
|
||||
// readonly based on detail division
|
||||
division_id: props.divisionId,
|
||||
|
||||
specialist_id: specialist.value || null,
|
||||
employee_id: employee.value || null,
|
||||
headStatus: headStatus.value !== undefined ? headStatus.value : undefined,
|
||||
}
|
||||
@@ -98,7 +98,7 @@ function onCancelForm() {
|
||||
|
||||
<template>
|
||||
<form
|
||||
id="form-division-position"
|
||||
id="form-specialist-position"
|
||||
@submit.prevent
|
||||
>
|
||||
<Block
|
||||
@@ -107,7 +107,7 @@ function onCancelForm() {
|
||||
:colCount="1"
|
||||
>
|
||||
<Cell>
|
||||
<Label height="compact">Kode Jabatan</Label>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input
|
||||
id="code"
|
||||
@@ -118,7 +118,7 @@ function onCancelForm() {
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama Jabatan</Label>
|
||||
<Label height="compact">Nama Posisi</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input
|
||||
id="name"
|
||||
@@ -129,7 +129,22 @@ function onCancelForm() {
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Pengisi Jabatan</Label>
|
||||
<Label height="compact">Spesialis</Label>
|
||||
<Field :errMessage="errors.specialist_id">
|
||||
<Combobox
|
||||
id="specialist"
|
||||
v-model="specialist"
|
||||
v-bind="specialistAttrs"
|
||||
:items="specialists"
|
||||
:is-disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Spesialis"
|
||||
search-placeholder="Cari Spesialis"
|
||||
empty-message="Item tidak ditemukan"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Karyawan</Label>
|
||||
<Field :errMessage="errors.employee_id">
|
||||
<Combobox
|
||||
id="employee"
|
||||
|
||||
@@ -13,14 +13,14 @@ export const config: Config = {
|
||||
[
|
||||
{ label: 'Kode Posisi' },
|
||||
{ label: 'Nama Posisi' },
|
||||
{ label: 'Nama Divisi ' },
|
||||
{ label: 'Nama Spesialis ' },
|
||||
{ label: 'Karyawan' },
|
||||
{ label: 'Status Kepala' },
|
||||
{ label: '' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['code', 'name', 'division', 'employee', 'head', 'action'],
|
||||
keys: ['code', 'name', 'specialist.name', 'employee', 'head', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
@@ -28,10 +28,6 @@ export const config: Config = {
|
||||
],
|
||||
|
||||
parses: {
|
||||
division: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.division?.name || '-'
|
||||
},
|
||||
employee: (rec: unknown): unknown => {
|
||||
const recX = rec as DivisionPosition
|
||||
const fullName = [recX.employee?.person.frontTitle, recX.employee?.person.name, recX.employee?.person.endTitle]
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import AppSpecialistList from '~/components/app/specialist/list.vue'
|
||||
import AppSpecialistEntryForm from '~/components/app/specialist/entry-form.vue'
|
||||
import AppSpecialistPositionList from '~/components/app/specialist-position/list.vue'
|
||||
import AppSpecialistPositionEntryForm from '~/components/app/specialist-position/entry-form.vue'
|
||||
|
||||
// Helpers
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { config } from '~/components/app/specialist-position/list.cfg'
|
||||
|
||||
// Types
|
||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
import { SpecialistSchema, type SpecialistFormData } from '~/schemas/specialist.schema'
|
||||
import { type SpecialistPositionFormData, SpecialistPositionSchema } from '~/schemas/specialist-position.schema'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
@@ -28,13 +29,15 @@ import {
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/specialist.handler'
|
||||
} from '~/handlers/specialist-position.handler'
|
||||
|
||||
// Services
|
||||
import { getList, getDetail } from '~/services/specialist.service'
|
||||
import { getValueLabelList as getUnitList } from '~/services/unit.service'
|
||||
import { getList, getDetail } from '~/services/specialist-position.service'
|
||||
import { getValueLabelList as getValueLabelSpecialistList } from '~/services/specialist.service'
|
||||
import { getValueLabelList as getEmployeeLabelList } from '~/services/employee.service'
|
||||
|
||||
const units = ref<{ value: string | number; label: string }[]>([])
|
||||
const specialists = ref<{ value: string | number; label: string }[]>([])
|
||||
const employees = ref<{ value: string | number; label: string }[]>([])
|
||||
const title = ref('')
|
||||
|
||||
const {
|
||||
@@ -52,11 +55,11 @@ const {
|
||||
sort: 'createdAt:asc',
|
||||
'page-number': params['page-number'] || 0,
|
||||
'page-size': params['page-size'] || 10,
|
||||
includes: 'unit',
|
||||
includes: 'specialist,Employee.Person',
|
||||
})
|
||||
return { success: result.success || false, body: result.body || {} }
|
||||
},
|
||||
entityName: 'specialist',
|
||||
entityName: 'specialist-position',
|
||||
})
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
@@ -98,18 +101,17 @@ const getCurrentSpecialistDetail = async (id: number | string) => {
|
||||
isFormEntryDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for row actions when recId or recAction changes
|
||||
watch([recId, recAction], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
getCurrentSpecialistDetail(recId.value)
|
||||
title.value = 'Detail Spesialis'
|
||||
title.value = 'Detail Spesialis Posisi'
|
||||
isReadonly.value = true
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
getCurrentSpecialistDetail(recId.value)
|
||||
title.value = 'Edit Spesialis'
|
||||
title.value = 'Edit Spesialis Posisi'
|
||||
isReadonly.value = false
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
@@ -119,8 +121,18 @@ watch([recId, recAction], () => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
units.value = await getUnitList()
|
||||
await getSpecialistList()
|
||||
try {
|
||||
specialists.value = await getValueLabelSpecialistList({ sort: 'createdAt:asc', 'page-size': 100 })
|
||||
employees.value = await getEmployeeLabelList({ sort: 'createdAt:asc', 'page-size': 100, includes: 'person' })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
// show toast
|
||||
toast({
|
||||
title: 'Terjadi Kesalahan',
|
||||
description: 'Terjadi kesalahan saat memuat data',
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -131,13 +143,11 @@ onMounted(async () => {
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<AppSpecialistPositionList
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
v-model:open="isFormEntryDialogOpen"
|
||||
:title="!!recItem ? title : 'Tambah Spesialis'"
|
||||
@@ -151,13 +161,14 @@ onMounted(async () => {
|
||||
"
|
||||
>
|
||||
<AppSpecialistPositionEntryForm
|
||||
:schema="SpecialistSchema"
|
||||
:units="units"
|
||||
:schema="SpecialistPositionSchema"
|
||||
:specialists="specialists"
|
||||
:employees="employees"
|
||||
:values="recItem"
|
||||
:is-loading="isProcessing"
|
||||
:is-readonly="isReadonly"
|
||||
@submit="
|
||||
(values: SpecialistFormData | Record<string, any>, resetForm: () => void) => {
|
||||
(values: SpecialistPositionFormData | Record<string, any>, resetForm: () => void) => {
|
||||
if (recId > 0) {
|
||||
handleActionEdit(recId, values, getSpecialistList, resetForm, toast)
|
||||
return
|
||||
@@ -168,7 +179,6 @@ onMounted(async () => {
|
||||
@cancel="handleCancelForm"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<!-- Record Confirmation Modal -->
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
@@ -178,18 +188,14 @@ onMounted(async () => {
|
||||
@cancel=""
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
<p>
|
||||
<strong>ID:</strong>
|
||||
{{ record?.id }}
|
||||
</p>
|
||||
<p v-if="record?.name">
|
||||
<strong>Nama:</strong>
|
||||
{{ record.name }}
|
||||
</p>
|
||||
<p v-if="record?.code">
|
||||
<strong>Kode:</strong>
|
||||
{{ record.code }}
|
||||
<div class="space-y-1 text-sm">
|
||||
<p
|
||||
v-for="field in config.delKeyNames"
|
||||
:key="field.key"
|
||||
:v-if="record?.[field.key]"
|
||||
>
|
||||
<span class="font-semibold">{{ field.label }}:</span>
|
||||
{{ record[field.key] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Handlers
|
||||
import { genCrudHandler } from '~/handlers/_handler'
|
||||
|
||||
// Services
|
||||
import { create, update, remove } from '~/services/specialist-position.service'
|
||||
|
||||
export const {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} = genCrudHandler({
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod'
|
||||
import type { SpecialistPosition } from '~/models/specialist-position'
|
||||
|
||||
const SpecialistPositionSchema = z.object({
|
||||
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
||||
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
||||
headStatus: z.boolean().optional().nullable(),
|
||||
specialist_id: z
|
||||
.union([
|
||||
z.string({ required_error: 'Spesialis harus diisi' }),
|
||||
z.number({ required_error: 'Spesialis harus diisi' }),
|
||||
])
|
||||
.optional()
|
||||
.nullable(),
|
||||
employee_id: z
|
||||
.union([z.string({ required_error: 'Karyawan harus diisi' }), z.number({ required_error: 'Karyawan harus diisi' })])
|
||||
.optional()
|
||||
.nullable(),
|
||||
})
|
||||
|
||||
type SpecialistPositionFormData = z.infer<typeof SpecialistPositionSchema> & Partial<SpecialistPosition>
|
||||
|
||||
export { SpecialistPositionSchema }
|
||||
export type { SpecialistPositionFormData }
|
||||
@@ -0,0 +1,25 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/specialist-position'
|
||||
const name = 'specialist-position'
|
||||
|
||||
export function create(data: any) {
|
||||
return base.create(path, data, name)
|
||||
}
|
||||
|
||||
export function getList(params: any = null) {
|
||||
return base.getList(path, params, name)
|
||||
}
|
||||
|
||||
export function getDetail(id: number | string) {
|
||||
return base.getDetail(path, id, name)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
Reference in New Issue
Block a user