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:
Khafid Prayoga
2025-10-31 10:55:45 +07:00
parent bf441ff714
commit c340de3114
6 changed files with 143 additions and 53 deletions
@@ -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]