From df27262bd1d017f4b0a1026e3060575c9a480a4e Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Fri, 24 Oct 2025 12:48:05 +0700 Subject: [PATCH 01/20] impl get position over detail divions wip: detail division for entry new division position finish v1 divison-position --- .../app/division-position/entry.vue | 192 ++++++++++++++++ app/components/app/division/detail/index.vue | 34 +++ .../app/division/detail/list-cfg.ts | 62 +++++ app/components/app/division/detail/list.vue | 39 ++++ app/components/content/division/detail.vue | 214 ++++++++++++++++++ app/models/division-position.ts | 4 +- app/models/division.ts | 7 +- app/models/installation-position.ts | 20 ++ app/models/specialist-position.ts | 20 ++ app/models/subspecialist-position.ts | 20 ++ app/models/unit-position.ts | 20 ++ .../org-src/division/[id]/index.vue | 42 ++++ app/services/employee.service.ts | 7 +- 13 files changed, 675 insertions(+), 6 deletions(-) create mode 100644 app/components/app/division-position/entry.vue create mode 100644 app/components/app/division/detail/index.vue create mode 100644 app/components/app/division/detail/list-cfg.ts create mode 100644 app/components/app/division/detail/list.vue create mode 100644 app/components/content/division/detail.vue create mode 100644 app/models/installation-position.ts create mode 100644 app/models/specialist-position.ts create mode 100644 app/models/subspecialist-position.ts create mode 100644 app/models/unit-position.ts create mode 100644 app/pages/(features)/org-src/division/[id]/index.vue diff --git a/app/components/app/division-position/entry.vue b/app/components/app/division-position/entry.vue new file mode 100644 index 00000000..da4782c0 --- /dev/null +++ b/app/components/app/division-position/entry.vue @@ -0,0 +1,192 @@ + + + diff --git a/app/components/app/division/detail/index.vue b/app/components/app/division/detail/index.vue new file mode 100644 index 00000000..dd5a4c59 --- /dev/null +++ b/app/components/app/division/detail/index.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/app/components/app/division/detail/list-cfg.ts b/app/components/app/division/detail/list-cfg.ts new file mode 100644 index 00000000..235a20cc --- /dev/null +++ b/app/components/app/division/detail/list-cfg.ts @@ -0,0 +1,62 @@ +import type { Config, RecComponent } from '~/components/pub/my-ui/data-table' +import { defineAsyncComponent } from 'vue' +import type { DivisionPosition } from '~/models/division-position' + +type SmallDetailDto = any + +const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue')) + +export const config: Config = { + cols: [{}, {}, {}, {}, {}, { width: 50 }], + + headers: [ + [ + { label: 'Kode Jabatan' }, + { label: 'Nama Jabatan' }, + { label: 'Pengisi Jabatan' }, + { label: 'Status Kepala' }, + { label: '' }, + ], + ], + + keys: ['code', 'name', 'employee', 'head', 'action'], + + delKeyNames: [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, + ], + + 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}`.trim() + + return fullName || '-' + }, + head: (rec: unknown): unknown => { + const recX = rec as SmallDetailDto + return recX.headStatus ? 'Ya' : 'Tidak' + }, + }, + + components: { + action(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: action, + props: { + size: 'sm', + }, + } + return res + }, + }, + + htmls: {}, +} diff --git a/app/components/app/division/detail/list.vue b/app/components/app/division/detail/list.vue new file mode 100644 index 00000000..2f7908cb --- /dev/null +++ b/app/components/app/division/detail/list.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/components/content/division/detail.vue b/app/components/content/division/detail.vue new file mode 100644 index 00000000..fe2641c6 --- /dev/null +++ b/app/components/content/division/detail.vue @@ -0,0 +1,214 @@ + + + diff --git a/app/models/division-position.ts b/app/models/division-position.ts index 3bd5e63a..dc263ada 100644 --- a/app/models/division-position.ts +++ b/app/models/division-position.ts @@ -1,11 +1,13 @@ import { type Base, genBase } from './_base' - +import type { Employee } from './employee' export interface DivisionPosition extends Base { code: string name: string headStatus?: boolean division_id: number employee_id?: number + + employee?: Employee | null } export function genDivisionPosition(): DivisionPosition { diff --git a/app/models/division.ts b/app/models/division.ts index 0f6d9185..c54a0e24 100644 --- a/app/models/division.ts +++ b/app/models/division.ts @@ -1,10 +1,13 @@ -import { type Base, genBase } from "./_base" - +import { type Base, genBase } from './_base' +import type { DivisionPosition } from './division-position' export interface Division extends Base { code: string name: string parent_id?: number | null childrens?: Division[] | null + + // preload + divisionPosition?: DivisionPosition[] | null } export function genDivision(): Division { diff --git a/app/models/installation-position.ts b/app/models/installation-position.ts new file mode 100644 index 00000000..aa0a9ebf --- /dev/null +++ b/app/models/installation-position.ts @@ -0,0 +1,20 @@ +import { type Base, genBase } from './_base' + +export interface InstallationPosition extends Base { + installation_id: number + code: string + name: string + headStatus?: boolean + employee_id?: number +} + +export function genInstallationPosition(): InstallationPosition { + return { + ...genBase(), + installation_id: 0, + code: '', + name: '', + headStatus: false, + employee_id: 0, + } +} diff --git a/app/models/specialist-position.ts b/app/models/specialist-position.ts new file mode 100644 index 00000000..58c53f9e --- /dev/null +++ b/app/models/specialist-position.ts @@ -0,0 +1,20 @@ +import { type Base, genBase } from './_base' + +export interface SpecialistPosition extends Base { + specialist_id: number + code: string + name: string + headStatus?: boolean + employee_id?: number +} + +export function genSpecialistPosition(): SpecialistPosition { + return { + ...genBase(), + specialist_id: 0, + code: '', + name: '', + headStatus: false, + employee_id: 0, + } +} diff --git a/app/models/subspecialist-position.ts b/app/models/subspecialist-position.ts new file mode 100644 index 00000000..5fdfa639 --- /dev/null +++ b/app/models/subspecialist-position.ts @@ -0,0 +1,20 @@ +import { type Base, genBase } from './_base' + +export interface SubSpecialistPosition extends Base { + subspecialist_id: number + code: string + name: string + headStatus?: boolean + employee_id?: number +} + +export function genSubSpecialistPosition(): SubSpecialistPosition { + return { + ...genBase(), + subspecialist_id: 0, + code: '', + name: '', + headStatus: false, + employee_id: 0, + } +} diff --git a/app/models/unit-position.ts b/app/models/unit-position.ts new file mode 100644 index 00000000..14925584 --- /dev/null +++ b/app/models/unit-position.ts @@ -0,0 +1,20 @@ +import { type Base, genBase } from './_base' + +export interface UnitPosition extends Base { + unit_id: number + code: string + name: string + headStatus?: boolean + employee_id?: number +} + +export function genUnitPosition(): UnitPosition { + return { + ...genBase(), + unit_id: 0, + code: '', + name: '', + headStatus: false, + employee_id: 0, + } +} diff --git a/app/pages/(features)/org-src/division/[id]/index.vue b/app/pages/(features)/org-src/division/[id]/index.vue new file mode 100644 index 00000000..658652d9 --- /dev/null +++ b/app/pages/(features)/org-src/division/[id]/index.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/services/employee.service.ts b/app/services/employee.service.ts index d3691c83..3121e7a2 100644 --- a/app/services/employee.service.ts +++ b/app/services/employee.service.ts @@ -1,4 +1,5 @@ // Base +import type { Employee } from '~/models/employee' import * as base from './_crud-base' const path = '/api/v1/employee' @@ -29,9 +30,9 @@ export async function getValueLabelList(params: any = null): Promise<{ value: st const result = await getList(params) if (result.success) { const resultData = result.body?.data || [] - data = resultData.map((item: any) => ({ - value: item.id ? Number(item.id) : item.code, - label: item.name, + data = resultData.map((item: Employee) => ({ + value: item.id, + label: `${item.person.frontTitle} ${item.person.name} ${item.person.endTitle}`.trim(), })) } return data From b16d3f108f08a4831c25e6ba763d8e02a6f34391 Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Mon, 27 Oct 2025 11:05:32 +0700 Subject: [PATCH 02/20] refactor(division): improve employee name formatting and navigation - Simplify employee name construction using array filtering - Replace dropdown-action-ud with dropdown-action-dud component - Update division detail navigation to use route navigation - Clean up code formatting and remove unnecessary whitespace --- app/components/app/division/detail/list-cfg.ts | 6 ++++-- app/components/app/division/list-cfg.ts | 11 +++-------- app/components/content/division/list.vue | 12 ++++++++---- .../pub/my-ui/data/dropdown-action-dud.vue | 11 ++++++++--- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/components/app/division/detail/list-cfg.ts b/app/components/app/division/detail/list-cfg.ts index 235a20cc..15e7b7e9 100644 --- a/app/components/app/division/detail/list-cfg.ts +++ b/app/components/app/division/detail/list-cfg.ts @@ -33,8 +33,10 @@ export const config: Config = { }, employee: (rec: unknown): unknown => { const recX = rec as DivisionPosition - const fullName = - `${recX.employee?.person.frontTitle} ${recX.employee?.person.name} ${recX.employee?.person.endTitle}`.trim() + const fullName = [recX.employee?.person.frontTitle, recX.employee?.person.name, recX.employee?.person.endTitle] + .filter(Boolean) + .join(' ') + .trim() return fullName || '-' }, diff --git a/app/components/app/division/list-cfg.ts b/app/components/app/division/list-cfg.ts index 54ed06a6..2d7f93ba 100644 --- a/app/components/app/division/list-cfg.ts +++ b/app/components/app/division/list-cfg.ts @@ -3,17 +3,12 @@ import { defineAsyncComponent } from 'vue' type SmallDetailDto = any -const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue')) +const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue')) export const config: Config = { cols: [{}, {}, {}, { width: 50 }], - headers: [[ - { label: 'Kode' }, - { label: 'Nama' }, - { label: 'Divisi Induk' }, - { label: '' }, - ]], + headers: [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Divisi Induk' }, { label: '' }]], keys: ['code', 'name', 'parent', 'action'], @@ -44,4 +39,4 @@ export const config: Config = { }, htmls: {}, -} \ No newline at end of file +} diff --git a/app/components/content/division/list.vue b/app/components/content/division/list.vue index 9ccefc56..d9294491 100644 --- a/app/components/content/division/list.vue +++ b/app/components/content/division/list.vue @@ -13,7 +13,7 @@ import { toast } from '~/components/pub/ui/toast' // Types import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types' import { DivisionSchema, type DivisionFormData } from '~/schemas/division.schema' -import type { Division } from "~/models/division" +import type { Division } from '~/models/division' import type { TreeItem } from '~/models/_base' // Handlers @@ -104,9 +104,13 @@ const getCurrentDivisionDetail = async (id: number | string) => { watch([recId, recAction], () => { switch (recAction.value) { case ActionEvents.showDetail: - getCurrentDivisionDetail(recId.value) - title.value = 'Detail Divisi' - isReadonly.value = true + navigateTo({ + name: 'org-src-division-id', + params: { + id: Number(recId.value), + }, + }) + break case ActionEvents.showEdit: getCurrentDivisionDetail(recId.value) diff --git a/app/components/pub/my-ui/data/dropdown-action-dud.vue b/app/components/pub/my-ui/data/dropdown-action-dud.vue index 71979c7c..dfcf1ada 100644 --- a/app/components/pub/my-ui/data/dropdown-action-dud.vue +++ b/app/components/pub/my-ui/data/dropdown-action-dud.vue @@ -2,9 +2,14 @@ import type { LinkItem, ListItemDto } from './types' import { ActionEvents } from './types' -const props = defineProps<{ +interface Props { rec: ListItemDto -}>() + size?: 'default' | 'sm' | 'lg' +} + +const props = withDefaults(defineProps(), { + size: 'lg', +}) const recId = inject>('rec_id')! const recAction = inject>('rec_action')! @@ -58,7 +63,7 @@ function del() { Date: Mon, 27 Oct 2025 13:22:10 +0700 Subject: [PATCH 03/20] feat(division-detail): add row numbering and remove pagination - Add index column to division position list table - Remove pagination component as it's no longer needed - Map data to include row numbers before display --- app/components/app/division/detail/list-cfg.ts | 3 ++- app/components/app/division/detail/list.vue | 4 ---- app/components/content/division/detail.vue | 11 ++++++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/components/app/division/detail/list-cfg.ts b/app/components/app/division/detail/list-cfg.ts index 15e7b7e9..76c3b9c8 100644 --- a/app/components/app/division/detail/list-cfg.ts +++ b/app/components/app/division/detail/list-cfg.ts @@ -11,6 +11,7 @@ export const config: Config = { headers: [ [ + { label: '#' }, { label: 'Kode Jabatan' }, { label: 'Nama Jabatan' }, { label: 'Pengisi Jabatan' }, @@ -19,7 +20,7 @@ export const config: Config = { ], ], - keys: ['code', 'name', 'employee', 'head', 'action'], + keys: ['index', 'code', 'name', 'employee', 'head', 'action'], delKeyNames: [ { key: 'code', label: 'Kode' }, diff --git a/app/components/app/division/detail/list.vue b/app/components/app/division/detail/list.vue index 2f7908cb..56198aec 100644 --- a/app/components/app/division/detail/list.vue +++ b/app/components/app/division/detail/list.vue @@ -31,9 +31,5 @@ function handlePageChange(page: number) { :rows="data" :skeleton-size="paginationMeta?.pageSize" /> - diff --git a/app/components/content/division/detail.vue b/app/components/content/division/detail.vue index fe2641c6..b7c1a6ad 100644 --- a/app/components/content/division/detail.vue +++ b/app/components/content/division/detail.vue @@ -70,12 +70,21 @@ const { sort: 'createdAt:asc', 'page-number': params['page-number'] || 0, 'page-size': params['page-size'] || 10, + 'page-no-limit': true, }) return { success: result.success || false, body: result.body || {} } }, entityName: 'division-position', }) +const dataMap = computed(() => { + return data.value.map((v, i) => { + return { + ...v, + index: i + 1, + } + }) +}) const headerPrep: HeaderPrep = { title: 'Detail Divisi', icon: 'i-lucide-user', @@ -154,7 +163,7 @@ watch([recId, recAction], () => {
From 16a4fc5d7f3ba8fe49ccd1fed56b07299e311fa8 Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Mon, 27 Oct 2025 13:32:56 +0700 Subject: [PATCH 04/20] fix(division-detail): handle errors when fetching division and employee data Add try-catch block to handle potential errors during API calls for division details and employee list. Show toast notification when errors occur to improve user feedback. --- app/components/content/division/detail.vue | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/components/content/division/detail.vue b/app/components/content/division/detail.vue index b7c1a6ad..8b7d2e92 100644 --- a/app/components/content/division/detail.vue +++ b/app/components/content/division/detail.vue @@ -115,11 +115,22 @@ const headerPrep: HeaderPrep = { // #region Lifecycle Hooks onMounted(async () => { - const result = await getDetailDivision(props.divisionId) - if (result.success) { - division.value = result.body.data || {} + try { + const result = await getDetailDivision(props.divisionId) + if (result.success) { + division.value = result.body.data || {} + } + + const res = await getEmployeeLabelList({ sort: 'createdAt:asc', 'page-size': 100, includes: 'person' }) + employees.value = res + } catch (err) { + // show toast + toast({ + title: 'Terjadi Kesalahan', + description: 'Terjadi kesalahan saat memuat data', + variant: 'destructive', + }) } - employees.value = await getEmployeeLabelList({ sort: 'createdAt:asc', 'page-size': 100, includes: 'person' }) }) // #endregion From af932ebfbc81a6b0664511b28deb092b6729e54f Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Mon, 27 Oct 2025 14:31:09 +0700 Subject: [PATCH 05/20] feat(org-src): add division position management page - Add new division position page to side menu - Implement division position list with improved employee name display - Include necessary API calls with proper includes for related data - Add error handling for data loading failures --- .../app/division-position/list-cfg.ts | 16 ++++--- .../content/division-position/list.vue | 25 +++++++---- .../org-src/division-position/index.vue | 42 +++++++++++++++++++ public/side-menu-items/sys.json | 4 ++ 4 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 app/pages/(features)/org-src/division-position/index.vue diff --git a/app/components/app/division-position/list-cfg.ts b/app/components/app/division-position/list-cfg.ts index e35627d2..cc153cc5 100644 --- a/app/components/app/division-position/list-cfg.ts +++ b/app/components/app/division-position/list-cfg.ts @@ -1,5 +1,6 @@ import type { Config, RecComponent } from '~/components/pub/my-ui/data-table' import { defineAsyncComponent } from 'vue' +import type { DivisionPosition } from '~/models/division-position' type SmallDetailDto = any @@ -10,9 +11,9 @@ export const config: Config = { headers: [ [ - { label: 'Kode' }, - { label: 'Nama' }, - { label: 'Divisi Induk' }, + { label: 'Kode Posisi' }, + { label: 'Nama Posisi' }, + { label: 'Nama Divisi ' }, { label: 'Karyawan' }, { label: 'Status Kepala' }, { label: '' }, @@ -32,8 +33,13 @@ export const config: Config = { return recX.division?.name || '-' }, employee: (rec: unknown): unknown => { - const recX = rec as SmallDetailDto - return recX.employee?.name || '-' + const recX = rec as DivisionPosition + const fullName = [recX.employee?.person.frontTitle, recX.employee?.person.name, recX.employee?.person.endTitle] + .filter(Boolean) + .join(' ') + .trim() + + return fullName || '-' }, head: (rec: unknown): unknown => { const recX = rec as SmallDetailDto diff --git a/app/components/content/division-position/list.vue b/app/components/content/division-position/list.vue index 019e64a9..8fafd960 100644 --- a/app/components/content/division-position/list.vue +++ b/app/components/content/division-position/list.vue @@ -54,6 +54,7 @@ const { sort: 'createdAt:asc', 'page-number': params['page-number'] || 0, 'page-size': params['page-size'] || 10, + includes: 'division,Employee.Person', }) return { success: result.success || false, body: result.body || {} } }, @@ -61,7 +62,7 @@ const { }) const headerPrep: HeaderPrep = { - title: 'Divisi', + title: 'Divisi Position', icon: 'i-lucide-box', refSearchNav: { placeholder: 'Cari (min. 3 karakter)...', @@ -105,12 +106,12 @@ watch([recId, recAction], () => { switch (recAction.value) { case ActionEvents.showDetail: getCurrentDivisionDetail(recId.value) - title.value = 'Detail Divisi' + title.value = 'Detail Divisi Position' isReadonly.value = true break case ActionEvents.showEdit: getCurrentDivisionDetail(recId.value) - title.value = 'Edit Divisi' + title.value = 'Edit Divisi Position' isReadonly.value = false break case ActionEvents.showConfirmDelete: @@ -120,9 +121,19 @@ watch([recId, recAction], () => { }) onMounted(async () => { - divisions.value = await getDivisionLabelList({ sort: 'createdAt:asc', 'page-size': 100 }) - employees.value = await getEmployeeLabelList({ sort: 'createdAt:asc', 'page-size': 100 }) - await getDivisionList() + try { + divisions.value = await getDivisionLabelList({ sort: 'createdAt:asc', 'page-size': 100 }) + employees.value = await getEmployeeLabelList({ sort: 'createdAt:asc', 'page-size': 100, includes: 'person' }) + await getDivisionList() + } catch (err) { + console.log(err) + // show toast + toast({ + title: 'Terjadi Kesalahan', + description: 'Terjadi kesalahan saat memuat data', + variant: 'destructive', + }) + } }) @@ -142,7 +153,7 @@ onMounted(async () => { +// import type { PagePermission } from '~/models/role' +import Error from '~/components/pub/my-ui/error/error.vue' +// import { PAGE_PERMISSIONS } from '~/lib/page-permission' + +definePageMeta({ + // middleware: ['rbac'], + roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'], + title: 'Daftar Divisi', + contentFrame: 'cf-container-lg', +}) + +const route = useRoute() + +useHead({ + title: () => route.meta.title as string, +}) + +// const roleAccess: PagePermission = PAGE_PERMISSIONS['/patient'] + +// const { checkRole, hasReadAccess } = useRBAC() + +// // Check if user has access to this page +// const hasAccess = checkRole(roleAccess) +// if (!hasAccess) { +// navigateTo('/403') +// } + +// Define permission-based computed properties +// const canRead = hasReadAccess(roleAccess) +const canRead = true + + + diff --git a/public/side-menu-items/sys.json b/public/side-menu-items/sys.json index 19491069..941c9a35 100644 --- a/public/side-menu-items/sys.json +++ b/public/side-menu-items/sys.json @@ -320,6 +320,10 @@ "title": "Divisi", "link": "/org-src/division" }, + { + "title": "Divisi Position", + "link": "/org-src/division-position" + }, { "title": "Instalasi", "link": "/org-src/installation" From bc4f5ddb0b9fd42550335a30ad342786d30529ce Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Mon, 27 Oct 2025 16:36:24 +0700 Subject: [PATCH 06/20] wip: orgs position --- .../app/installation-position/entry-form.vue | 192 +++++++++++++++++ .../app/installation-position/list.cfg.ts | 65 ++++++ .../app/installation-position/list.vue | 39 ++++ .../app/specialist-position/entry-form.vue | 192 +++++++++++++++++ .../app/specialist-position/list.cfg.ts | 65 ++++++ .../app/specialist-position/list.vue | 39 ++++ .../app/subspecialist-position/entry-form.vue | 192 +++++++++++++++++ .../app/subspecialist-position/list.cfg.ts | 65 ++++++ .../app/subspecialist-position/list.vue | 39 ++++ .../app/unit-position/entry-form.vue | 192 +++++++++++++++++ app/components/app/unit-position/list.cfg.ts | 65 ++++++ app/components/app/unit-position/list.vue | 39 ++++ .../content/division-position/list.vue | 2 +- .../content/installation-position/list.vue | 196 +++++++++++++++++ .../content/specialist-position/list.vue | 197 ++++++++++++++++++ .../content/subspecialist-position/list.vue | 197 ++++++++++++++++++ app/components/content/unit-position/list.vue | 197 ++++++++++++++++++ .../org-src/installation-position/index.vue | 42 ++++ .../org-src/specialist-position/index.vue | 42 ++++ .../org-src/subspecialist-position/index.vue | 42 ++++ .../org-src/unit-position/index.vue | 42 ++++ public/side-menu-items/sys.json | 18 +- 22 files changed, 2157 insertions(+), 2 deletions(-) create mode 100644 app/components/app/installation-position/entry-form.vue create mode 100644 app/components/app/installation-position/list.cfg.ts create mode 100644 app/components/app/installation-position/list.vue create mode 100644 app/components/app/specialist-position/entry-form.vue create mode 100644 app/components/app/specialist-position/list.cfg.ts create mode 100644 app/components/app/specialist-position/list.vue create mode 100644 app/components/app/subspecialist-position/entry-form.vue create mode 100644 app/components/app/subspecialist-position/list.cfg.ts create mode 100644 app/components/app/subspecialist-position/list.vue create mode 100644 app/components/app/unit-position/entry-form.vue create mode 100644 app/components/app/unit-position/list.cfg.ts create mode 100644 app/components/app/unit-position/list.vue create mode 100644 app/components/content/installation-position/list.vue create mode 100644 app/components/content/specialist-position/list.vue create mode 100644 app/components/content/subspecialist-position/list.vue create mode 100644 app/components/content/unit-position/list.vue create mode 100644 app/pages/(features)/org-src/installation-position/index.vue create mode 100644 app/pages/(features)/org-src/specialist-position/index.vue create mode 100644 app/pages/(features)/org-src/subspecialist-position/index.vue create mode 100644 app/pages/(features)/org-src/unit-position/index.vue diff --git a/app/components/app/installation-position/entry-form.vue b/app/components/app/installation-position/entry-form.vue new file mode 100644 index 00000000..da4782c0 --- /dev/null +++ b/app/components/app/installation-position/entry-form.vue @@ -0,0 +1,192 @@ + + + diff --git a/app/components/app/installation-position/list.cfg.ts b/app/components/app/installation-position/list.cfg.ts new file mode 100644 index 00000000..cc153cc5 --- /dev/null +++ b/app/components/app/installation-position/list.cfg.ts @@ -0,0 +1,65 @@ +import type { Config, RecComponent } from '~/components/pub/my-ui/data-table' +import { defineAsyncComponent } from 'vue' +import type { DivisionPosition } from '~/models/division-position' + +type SmallDetailDto = any + +const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue')) + +export const config: Config = { + cols: [{}, {}, {}, {}, {}, { width: 50 }], + + headers: [ + [ + { label: 'Kode Posisi' }, + { label: 'Nama Posisi' }, + { label: 'Nama Divisi ' }, + { label: 'Karyawan' }, + { label: 'Status Kepala' }, + { label: '' }, + ], + ], + + keys: ['code', 'name', 'division', 'employee', 'head', 'action'], + + delKeyNames: [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, + ], + + 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] + .filter(Boolean) + .join(' ') + .trim() + + return fullName || '-' + }, + head: (rec: unknown): unknown => { + const recX = rec as SmallDetailDto + return recX.headStatus ? 'Ya' : 'Tidak' + }, + }, + + components: { + action(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: action, + props: { + size: 'sm', + }, + } + return res + }, + }, + + htmls: {}, +} diff --git a/app/components/app/installation-position/list.vue b/app/components/app/installation-position/list.vue new file mode 100644 index 00000000..6ad7dd81 --- /dev/null +++ b/app/components/app/installation-position/list.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/components/app/specialist-position/entry-form.vue b/app/components/app/specialist-position/entry-form.vue new file mode 100644 index 00000000..da4782c0 --- /dev/null +++ b/app/components/app/specialist-position/entry-form.vue @@ -0,0 +1,192 @@ + + + diff --git a/app/components/app/specialist-position/list.cfg.ts b/app/components/app/specialist-position/list.cfg.ts new file mode 100644 index 00000000..cc153cc5 --- /dev/null +++ b/app/components/app/specialist-position/list.cfg.ts @@ -0,0 +1,65 @@ +import type { Config, RecComponent } from '~/components/pub/my-ui/data-table' +import { defineAsyncComponent } from 'vue' +import type { DivisionPosition } from '~/models/division-position' + +type SmallDetailDto = any + +const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue')) + +export const config: Config = { + cols: [{}, {}, {}, {}, {}, { width: 50 }], + + headers: [ + [ + { label: 'Kode Posisi' }, + { label: 'Nama Posisi' }, + { label: 'Nama Divisi ' }, + { label: 'Karyawan' }, + { label: 'Status Kepala' }, + { label: '' }, + ], + ], + + keys: ['code', 'name', 'division', 'employee', 'head', 'action'], + + delKeyNames: [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, + ], + + 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] + .filter(Boolean) + .join(' ') + .trim() + + return fullName || '-' + }, + head: (rec: unknown): unknown => { + const recX = rec as SmallDetailDto + return recX.headStatus ? 'Ya' : 'Tidak' + }, + }, + + components: { + action(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: action, + props: { + size: 'sm', + }, + } + return res + }, + }, + + htmls: {}, +} diff --git a/app/components/app/specialist-position/list.vue b/app/components/app/specialist-position/list.vue new file mode 100644 index 00000000..6ad7dd81 --- /dev/null +++ b/app/components/app/specialist-position/list.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/components/app/subspecialist-position/entry-form.vue b/app/components/app/subspecialist-position/entry-form.vue new file mode 100644 index 00000000..da4782c0 --- /dev/null +++ b/app/components/app/subspecialist-position/entry-form.vue @@ -0,0 +1,192 @@ + + + diff --git a/app/components/app/subspecialist-position/list.cfg.ts b/app/components/app/subspecialist-position/list.cfg.ts new file mode 100644 index 00000000..cc153cc5 --- /dev/null +++ b/app/components/app/subspecialist-position/list.cfg.ts @@ -0,0 +1,65 @@ +import type { Config, RecComponent } from '~/components/pub/my-ui/data-table' +import { defineAsyncComponent } from 'vue' +import type { DivisionPosition } from '~/models/division-position' + +type SmallDetailDto = any + +const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue')) + +export const config: Config = { + cols: [{}, {}, {}, {}, {}, { width: 50 }], + + headers: [ + [ + { label: 'Kode Posisi' }, + { label: 'Nama Posisi' }, + { label: 'Nama Divisi ' }, + { label: 'Karyawan' }, + { label: 'Status Kepala' }, + { label: '' }, + ], + ], + + keys: ['code', 'name', 'division', 'employee', 'head', 'action'], + + delKeyNames: [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, + ], + + 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] + .filter(Boolean) + .join(' ') + .trim() + + return fullName || '-' + }, + head: (rec: unknown): unknown => { + const recX = rec as SmallDetailDto + return recX.headStatus ? 'Ya' : 'Tidak' + }, + }, + + components: { + action(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: action, + props: { + size: 'sm', + }, + } + return res + }, + }, + + htmls: {}, +} diff --git a/app/components/app/subspecialist-position/list.vue b/app/components/app/subspecialist-position/list.vue new file mode 100644 index 00000000..6ad7dd81 --- /dev/null +++ b/app/components/app/subspecialist-position/list.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/components/app/unit-position/entry-form.vue b/app/components/app/unit-position/entry-form.vue new file mode 100644 index 00000000..da4782c0 --- /dev/null +++ b/app/components/app/unit-position/entry-form.vue @@ -0,0 +1,192 @@ + + + diff --git a/app/components/app/unit-position/list.cfg.ts b/app/components/app/unit-position/list.cfg.ts new file mode 100644 index 00000000..cc153cc5 --- /dev/null +++ b/app/components/app/unit-position/list.cfg.ts @@ -0,0 +1,65 @@ +import type { Config, RecComponent } from '~/components/pub/my-ui/data-table' +import { defineAsyncComponent } from 'vue' +import type { DivisionPosition } from '~/models/division-position' + +type SmallDetailDto = any + +const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue')) + +export const config: Config = { + cols: [{}, {}, {}, {}, {}, { width: 50 }], + + headers: [ + [ + { label: 'Kode Posisi' }, + { label: 'Nama Posisi' }, + { label: 'Nama Divisi ' }, + { label: 'Karyawan' }, + { label: 'Status Kepala' }, + { label: '' }, + ], + ], + + keys: ['code', 'name', 'division', 'employee', 'head', 'action'], + + delKeyNames: [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, + ], + + 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] + .filter(Boolean) + .join(' ') + .trim() + + return fullName || '-' + }, + head: (rec: unknown): unknown => { + const recX = rec as SmallDetailDto + return recX.headStatus ? 'Ya' : 'Tidak' + }, + }, + + components: { + action(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: action, + props: { + size: 'sm', + }, + } + return res + }, + }, + + htmls: {}, +} diff --git a/app/components/app/unit-position/list.vue b/app/components/app/unit-position/list.vue new file mode 100644 index 00000000..6ad7dd81 --- /dev/null +++ b/app/components/app/unit-position/list.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/components/content/division-position/list.vue b/app/components/content/division-position/list.vue index 8fafd960..4fa8605d 100644 --- a/app/components/content/division-position/list.vue +++ b/app/components/content/division-position/list.vue @@ -62,7 +62,7 @@ const { }) const headerPrep: HeaderPrep = { - title: 'Divisi Position', + title: 'Divisi - Posisi', icon: 'i-lucide-box', refSearchNav: { placeholder: 'Cari (min. 3 karakter)...', diff --git a/app/components/content/installation-position/list.vue b/app/components/content/installation-position/list.vue new file mode 100644 index 00000000..68870570 --- /dev/null +++ b/app/components/content/installation-position/list.vue @@ -0,0 +1,196 @@ + + + diff --git a/app/components/content/specialist-position/list.vue b/app/components/content/specialist-position/list.vue new file mode 100644 index 00000000..d55abea1 --- /dev/null +++ b/app/components/content/specialist-position/list.vue @@ -0,0 +1,197 @@ + + + diff --git a/app/components/content/subspecialist-position/list.vue b/app/components/content/subspecialist-position/list.vue new file mode 100644 index 00000000..6ff2c2be --- /dev/null +++ b/app/components/content/subspecialist-position/list.vue @@ -0,0 +1,197 @@ + + + diff --git a/app/components/content/unit-position/list.vue b/app/components/content/unit-position/list.vue new file mode 100644 index 00000000..95a5568d --- /dev/null +++ b/app/components/content/unit-position/list.vue @@ -0,0 +1,197 @@ + + + diff --git a/app/pages/(features)/org-src/installation-position/index.vue b/app/pages/(features)/org-src/installation-position/index.vue new file mode 100644 index 00000000..0b525098 --- /dev/null +++ b/app/pages/(features)/org-src/installation-position/index.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/pages/(features)/org-src/specialist-position/index.vue b/app/pages/(features)/org-src/specialist-position/index.vue new file mode 100644 index 00000000..a2e29428 --- /dev/null +++ b/app/pages/(features)/org-src/specialist-position/index.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/pages/(features)/org-src/subspecialist-position/index.vue b/app/pages/(features)/org-src/subspecialist-position/index.vue new file mode 100644 index 00000000..650ae9ee --- /dev/null +++ b/app/pages/(features)/org-src/subspecialist-position/index.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/pages/(features)/org-src/unit-position/index.vue b/app/pages/(features)/org-src/unit-position/index.vue new file mode 100644 index 00000000..5395ea8e --- /dev/null +++ b/app/pages/(features)/org-src/unit-position/index.vue @@ -0,0 +1,42 @@ + + + diff --git a/public/side-menu-items/sys.json b/public/side-menu-items/sys.json index 941c9a35..b69127e7 100644 --- a/public/side-menu-items/sys.json +++ b/public/side-menu-items/sys.json @@ -321,24 +321,40 @@ "link": "/org-src/division" }, { - "title": "Divisi Position", + "title": "Divisi - Posisi", "link": "/org-src/division-position" }, { "title": "Instalasi", "link": "/org-src/installation" }, + { + "title": "Instalasi - Posisi", + "link": "/org-src/installation-position" + }, { "title": "Unit", "link": "/org-src/unit" }, + { + "title": "Unit - Posisi", + "link": "/org-src/unit-position" + }, { "title": "Spesialis", "link": "/org-src/specialist" }, + { + "title": "Spesialis - Posisi", + "link": "/org-src/specialist-position" + }, { "title": "Sub Spesialis", "link": "/org-src/subspecialist" + }, + { + "title": "Sub Spesialis - Posisi", + "link": "/org-src/subspecialist-position" } ] }, From dc653402c7cc4ef55108cd84fe52ce5659093cc2 Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Tue, 28 Oct 2025 12:45:57 +0700 Subject: [PATCH 07/20] feat(installation-position): implement crud operations and ui components - Add installation position handler, service, and schema - Update list configuration and entry form components - Enhance pagination component with configurable controls - Implement installation position list view with search and pagination --- .../app/installation-position/entry-form.vue | 45 ++++++--- .../app/installation-position/list.cfg.ts | 4 +- .../content/installation-position/list.vue | 58 +++++++----- .../pub/my-ui/pagination/pagination-view.vue | 12 ++- .../pub/my-ui/pagination/pagination.vue | 92 ++++++++++++++----- app/handlers/installation-position.handler.ts | 24 +++++ app/schemas/installation-position.schema.ts | 20 ++++ app/services/installation-position.service.ts | 41 +++++++++ 8 files changed, 229 insertions(+), 67 deletions(-) create mode 100644 app/handlers/installation-position.handler.ts create mode 100644 app/schemas/installation-position.schema.ts create mode 100644 app/services/installation-position.service.ts diff --git a/app/components/app/installation-position/entry-form.vue b/app/components/app/installation-position/entry-form.vue index da4782c0..4f7885a1 100644 --- a/app/components/app/installation-position/entry-form.vue +++ b/app/components/app/installation-position/entry-form.vue @@ -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 { InstallationPositionFormData } from '~/schemas/installation-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 { genInstallationPosition } from '~/models/installation-position' interface Props { schema: z.ZodSchema - divisionId: number + installations: any[] employees: any[] values: any isLoading?: boolean @@ -26,21 +26,21 @@ interface Props { } const props = defineProps() - 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: InstallationPositionFormData, resetForm: () => void] cancel: [resetForm: () => void] }>() const { defineField, errors, meta } = useForm({ validationSchema: toTypedSchema(props.schema), - initialValues: genDivisionPosition() as Partial, + initialValues: genInstallationPosition() as Partial, }) const [code, codeAttrs] = defineField('code') const [name, nameAttrs] = defineField('name') +const [installation, installationAttrs] = defineField('installation_id') const [employee, employeeAttrs] = defineField('employee_id') const [headStatus, headStatusAttrs] = defineField('headStatus') @@ -62,6 +62,8 @@ const headStatusStr = computed({ 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.installation_id !== undefined) + installation.value = props.values.installation_id ? Number(props.values.installation_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 = '' + installation.value = null employee.value = null headStatus.value = false } // Form submission handler function onSubmitForm() { - const formData: DivisionPositionFormData = { + const formData: InstallationPositionFormData = { ...genBase(), name: name.value || '', code: code.value || '', - - // readonly based on detail division - division_id: props.divisionId, - + installation_id: installation.value || null, employee_id: employee.value || null, headStatus: headStatus.value !== undefined ? headStatus.value : undefined, } @@ -98,7 +98,7 @@ function onCancelForm() { diff --git a/app/components/content/division/list.vue b/app/components/content/division/list.vue index d9294491..0456891b 100644 --- a/app/components/content/division/list.vue +++ b/app/components/content/division/list.vue @@ -78,6 +78,7 @@ const headerPrep: HeaderPrep = { label: 'Tambah', icon: 'i-lucide-plus', onClick: () => { + recAction.value = '' recItem.value = null recId.value = 0 isFormEntryDialogOpen.value = true @@ -104,12 +105,22 @@ const getCurrentDivisionDetail = async (id: number | string) => { watch([recId, recAction], () => { switch (recAction.value) { case ActionEvents.showDetail: - navigateTo({ - name: 'org-src-division-id', - params: { - id: Number(recId.value), - }, - }) + if (Number(recId.value) > 0) { + const id = Number(recId.value) + + recAction.value = '' + recItem.value = null + recId.value = 0 + isFormEntryDialogOpen.value = false + isReadonly.value = false + + navigateTo({ + name: 'org-src-division-id', + params: { + id, + }, + }) + } break case ActionEvents.showEdit: diff --git a/app/components/content/installation/detail.vue b/app/components/content/installation/detail.vue index e9721a5b..55a0dd05 100644 --- a/app/components/content/installation/detail.vue +++ b/app/components/content/installation/detail.vue @@ -9,7 +9,7 @@ import Header from '~/components/pub/my-ui/nav-header/prep.vue' import type { Installation } from '~/models/installation' import { getDetail as getDetailInstallation } from '~/services/installation.service' -// #region division positions +// #region installtaion positions import { config } from '~/components/app/installation/detail/list.cfg' // Helpers @@ -174,7 +174,7 @@ watch([recId, recAction], () => {
- { watch([recId, recAction], () => { switch (recAction.value) { case ActionEvents.showDetail: - navigateTo({ - name: 'org-src-installation-id', - params: { - id: recId.value, - }, - }) + if (Number(recId.value) > 0) { + const id = Number(recId.value) + + recAction.value = '' + recItem.value = null + recId.value = 0 + isFormEntryDialogOpen.value = false + isReadonly.value = false + + navigateTo({ + name: 'org-src-installation-id', + params: { + id, + }, + }) + } break case ActionEvents.showEdit: diff --git a/app/components/pub/my-ui/nav-footer/ba.vue b/app/components/pub/my-ui/nav-footer/ba.vue new file mode 100644 index 00000000..2b8e7767 --- /dev/null +++ b/app/components/pub/my-ui/nav-footer/ba.vue @@ -0,0 +1,25 @@ + + + From 1dc42be406aa6562074f61c33336f5dab1132dba Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Thu, 30 Oct 2025 12:14:32 +0700 Subject: [PATCH 10/20] feat(unit-position): implement crud operations and update ui components - Add new handler, service, and schema files for unit-position - Update list configuration and entry form components - Modify page title and integrate employee relation - Implement CRUD operations with proper validation --- .../app/unit-position/entry-form.vue | 44 +++++++---- app/components/app/unit-position/list.cfg.ts | 12 +-- app/components/content/unit-position/list.vue | 77 +++++++++++-------- app/handlers/unit-position.handler.ts | 24 ++++++ app/models/unit-position.ts | 3 + .../org-src/unit-position/index.vue | 2 +- app/schemas/unit-position.schema.ts | 24 ++++++ app/services/unit-position.service.ts | 25 ++++++ 8 files changed, 153 insertions(+), 58 deletions(-) create mode 100644 app/handlers/unit-position.handler.ts create mode 100644 app/schemas/unit-position.schema.ts create mode 100644 app/services/unit-position.service.ts diff --git a/app/components/app/unit-position/entry-form.vue b/app/components/app/unit-position/entry-form.vue index da4782c0..b6ab8609 100644 --- a/app/components/app/unit-position/entry-form.vue +++ b/app/components/app/unit-position/entry-form.vue @@ -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 { UnitPositionFormData } from '~/schemas/unit-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 { genUnitPosition } from '~/models/unit-position' interface Props { schema: z.ZodSchema - divisionId: number + units: any[] employees: any[] values: any isLoading?: boolean @@ -26,21 +26,21 @@ interface Props { } const props = defineProps() - 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: UnitPositionFormData, resetForm: () => void] cancel: [resetForm: () => void] }>() const { defineField, errors, meta } = useForm({ validationSchema: toTypedSchema(props.schema), - initialValues: genDivisionPosition() as Partial, + initialValues: genUnitPosition() as Partial, }) const [code, codeAttrs] = defineField('code') const [name, nameAttrs] = defineField('name') +const [unit, unitAttrs] = defineField('unit_id') const [employee, employeeAttrs] = defineField('employee_id') const [headStatus, headStatusAttrs] = defineField('headStatus') @@ -62,6 +62,7 @@ const headStatusStr = computed({ 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.unit_id !== undefined) unit.value = props.values.unit_id ? Number(props.values.unit_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 +71,18 @@ if (props.values) { const resetForm = () => { code.value = '' name.value = '' + unit.value = null employee.value = null headStatus.value = false } // Form submission handler function onSubmitForm() { - const formData: DivisionPositionFormData = { + const formData: UnitPositionFormData = { ...genBase(), name: name.value || '', code: code.value || '', - - // readonly based on detail division - division_id: props.divisionId, - + unit_id: unit.value || null, employee_id: employee.value || null, headStatus: headStatus.value !== undefined ? headStatus.value : undefined, } @@ -98,7 +97,7 @@ function onCancelForm() {