Dev/v1.2 (#4)
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -4,7 +4,7 @@ import * as select from '@zag-js/select'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Box } from '@/components/ui/box'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
|
||||
import { Field, FieldDescription, FieldError, FieldLabel } from '@/components/ui/field'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
SelectRoot,
|
||||
@@ -27,16 +27,19 @@ import type {
|
||||
MembershipApplicationHeirForm,
|
||||
MembershipApplicationReferenceForm,
|
||||
} from '../types/membership-application.types'
|
||||
import illustrationUrl from '@/assets/images/logo-kopkb.svg'
|
||||
import illustrationUrl from '@/assets/images/logo.svg'
|
||||
|
||||
const MAX_FILE_SIZE_MB = 10
|
||||
const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024
|
||||
const MIN_STOCK_MONTHLY_CONTRIBUTION = 50
|
||||
const INITIAL_MANDATORY_STOCK_MONTHLY_CONTRIBUTION = 84
|
||||
const MIN_FEE_MONTHLY_CONTRIBUTION = 30
|
||||
|
||||
const steps = [
|
||||
{ id: 1, label: 'Maklumat Peribadi' },
|
||||
{ id: 2, label: 'Hubungan & Alamat' },
|
||||
{ id: 3, label: 'Maklumat Pekerjaan' },
|
||||
{ id: 4, label: 'Maklumat Waris' },
|
||||
{ id: 4, label: 'Maklumat Penama' },
|
||||
{ id: 5, label: 'Dokumen & Hantar' },
|
||||
] as const
|
||||
|
||||
@@ -73,6 +76,31 @@ const RELATIONSHIP_OPTIONS: SelectOption[] = [
|
||||
{ label: 'Lain-lain', value: 'Lain-lain' },
|
||||
]
|
||||
|
||||
// TODO: replace with API lookup
|
||||
const EMPLOYERS = [
|
||||
{
|
||||
name: 'INFRA QUEST SDN BHD',
|
||||
address: 'Lot 1045, Jalan Dato’ Lundang, 15200 Kota Bharu, Kelantan',
|
||||
},
|
||||
{
|
||||
name: 'Permodalan Kelantan Berhad',
|
||||
address: 'Permodalan Kelantan Berhad, Tingkat 4, Wisma Permodalan Kelantan Berhad, Jalan Maju, 15000 Kota Bharu Kelantan',
|
||||
},
|
||||
{
|
||||
name: 'Koperasi Permodalan Kelantan Berhad',
|
||||
address: 'Lot Pt 448, Tingkat 1,Jalan Kuala Krai, Batu 3, Wakaf Che Yeh, 15150 Kota Bharu, Kelantan.',
|
||||
},
|
||||
{
|
||||
name: "An-Nisa'",
|
||||
address: 'Jln Sultan Ibrahim, Bandar Kota Bharu, 15050 Kota Bharu, Kelantan.',
|
||||
},
|
||||
] as const
|
||||
|
||||
const EMPLOYER_OPTIONS: SelectOption[] = EMPLOYERS.map((employer) => ({
|
||||
label: employer.name,
|
||||
value: employer.name,
|
||||
}))
|
||||
|
||||
function createSelectCollection(options: SelectOption[]) {
|
||||
return select.collection({
|
||||
items: options,
|
||||
@@ -94,6 +122,11 @@ function apiValueToLabel(options: SelectOption[], value: string | undefined): st
|
||||
const genderCollection = createSelectCollection(GENDER_OPTIONS)
|
||||
const marriageStatusCollection = createSelectCollection(MARRIAGE_STATUS_OPTIONS)
|
||||
const relationshipCollection = createSelectCollection(RELATIONSHIP_OPTIONS)
|
||||
const employerCollection = createSelectCollection(EMPLOYER_OPTIONS)
|
||||
|
||||
function getEmployerAddress(name: string): string {
|
||||
return EMPLOYERS.find((employer) => employer.name === name)?.address ?? ''
|
||||
}
|
||||
|
||||
function createEmptyReference(): MembershipApplicationReferenceForm {
|
||||
return {
|
||||
@@ -149,6 +182,7 @@ const genderInitial = computed(() => apiValueToLabel(GENDER_OPTIONS, form.applic
|
||||
const marriageStatusInitial = computed(() =>
|
||||
apiValueToLabel(MARRIAGE_STATUS_OPTIONS, form.applicant.marriage_status),
|
||||
)
|
||||
const employerInitial = computed(() => apiValueToLabel(EMPLOYER_OPTIONS, form.applicant.employer_name))
|
||||
|
||||
function setGenderValue(details: { value: string[] }) {
|
||||
form.applicant.gender = labelToApiValue(GENDER_OPTIONS, details.value[0])
|
||||
@@ -160,6 +194,14 @@ function setMarriageStatusValue(details: { value: string[] }) {
|
||||
delete fieldErrors['applicant.marriage_status']
|
||||
}
|
||||
|
||||
function setEmployerValue(details: { value: string[] }) {
|
||||
const employerName = labelToApiValue(EMPLOYER_OPTIONS, details.value[0])
|
||||
form.applicant.employer_name = employerName
|
||||
form.applicant.employer_address = getEmployerAddress(employerName)
|
||||
delete fieldErrors['applicant.employer_name']
|
||||
delete fieldErrors['applicant.employer_address']
|
||||
}
|
||||
|
||||
function setHeirRelationshipValue(index: number, details: { value: string[] }) {
|
||||
const heir = form.heirs[index]
|
||||
if (!heir) return
|
||||
@@ -254,7 +296,7 @@ const stepTitle = computed(() => {
|
||||
case 3:
|
||||
return 'Maklumat Pekerjaan & Caruman'
|
||||
case 4:
|
||||
return 'Maklumat Waris'
|
||||
return 'Maklumat Penama'
|
||||
default:
|
||||
return 'Dokumen & Pengesahan'
|
||||
}
|
||||
@@ -269,7 +311,7 @@ const stepDescription = computed(() => {
|
||||
case 3:
|
||||
return 'Masukkan maklumat pekerjaan dan caruman bulanan.'
|
||||
case 4:
|
||||
return 'Tambah sekurang-kurangnya satu waris.'
|
||||
return 'Tambah sekurang-kurangnya satu Penama.'
|
||||
default:
|
||||
return 'Muat naik dokumen sokongan dan semak maklumat sebelum hantar.'
|
||||
}
|
||||
@@ -324,21 +366,45 @@ function validateStep(step: number): boolean {
|
||||
requireField(
|
||||
'applicant.stock_monthly_contribution',
|
||||
form.applicant.stock_monthly_contribution,
|
||||
'Caruman saham bulanan',
|
||||
'Potongan modal syer minima',
|
||||
)
|
||||
|
||||
const stockContribution = Number(form.applicant.stock_monthly_contribution)
|
||||
if (
|
||||
form.applicant.stock_monthly_contribution &&
|
||||
(Number.isNaN(stockContribution) || stockContribution < MIN_STOCK_MONTHLY_CONTRIBUTION)
|
||||
) {
|
||||
setError(
|
||||
'applicant.stock_monthly_contribution',
|
||||
`Potongan modal syer minima mestilah sekurang-kurangnya RM${MIN_STOCK_MONTHLY_CONTRIBUTION}.`,
|
||||
)
|
||||
valid = false
|
||||
}
|
||||
requireField(
|
||||
'applicant.fee_monthly_contribution',
|
||||
form.applicant.fee_monthly_contribution,
|
||||
'Caruman yuran bulanan',
|
||||
'Potongan yuran bulanan',
|
||||
)
|
||||
|
||||
const feeContribution = Number(form.applicant.fee_monthly_contribution)
|
||||
if (
|
||||
form.applicant.fee_monthly_contribution &&
|
||||
(Number.isNaN(feeContribution) || feeContribution < MIN_FEE_MONTHLY_CONTRIBUTION)
|
||||
) {
|
||||
setError(
|
||||
'applicant.fee_monthly_contribution',
|
||||
`Potongan yuran bulanan mestilah sekurang-kurangnya RM${MIN_FEE_MONTHLY_CONTRIBUTION}.`,
|
||||
)
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
if (step === 4) {
|
||||
form.heirs.forEach((heir, index) => {
|
||||
requireField(`heirs.${index}.name`, heir.name, `Nama waris ${index + 1}`)
|
||||
requireField(`heirs.${index}.ic_number`, heir.ic_number, `No. KP waris ${index + 1}`)
|
||||
requireField(`heirs.${index}.relationship`, heir.relationship, `Hubungan waris ${index + 1}`)
|
||||
requireField(`heirs.${index}.phone_number`, heir.phone_number, `No. telefon waris ${index + 1}`)
|
||||
requireField(`heirs.${index}.name`, heir.name, `Nama Penama ${index + 1}`)
|
||||
requireField(`heirs.${index}.ic_number`, heir.ic_number, `No. KP Penama ${index + 1}`)
|
||||
requireField(`heirs.${index}.relationship`, heir.relationship, `Hubungan Penama ${index + 1}`)
|
||||
requireField(`heirs.${index}.phone_number`, heir.phone_number, `No. telefon Penama ${index + 1}`)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -355,6 +421,11 @@ function validateStep(step: number): boolean {
|
||||
setError('documents.ic_copy', 'Salinan kad pengenalan diperlukan.')
|
||||
valid = false
|
||||
}
|
||||
|
||||
if (!form.documents.employer_letter) {
|
||||
setError('documents.employer_letter', 'Surat pengesahan majikan diperlukan.')
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
return valid
|
||||
@@ -638,8 +709,23 @@ function stepLabelClass(stepId: number) {
|
||||
|
||||
<template v-else-if="currentStep === 3">
|
||||
<Field class="col-span-12 sm:col-span-6">
|
||||
<FieldLabel for="employer_name">Nama Majikan</FieldLabel>
|
||||
<Input id="employer_name" v-model="form.applicant.employer_name" type="text" />
|
||||
<FieldLabel>Nama Majikan</FieldLabel>
|
||||
<SelectRoot :key="`employer-${form.applicant.employer_name}`" class="w-full"
|
||||
:collection="employerCollection" :default-value="employerInitial" @value-change="setEmployerValue">
|
||||
<SelectControl>
|
||||
<SelectTrigger :aria-invalid="!!fieldErrors['applicant.employer_name']">
|
||||
<SelectValueText placeholder="Pilih majikan" />
|
||||
</SelectTrigger>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
<SelectItemGroup>
|
||||
<SelectItemGroupLabel>Nama Majikan</SelectItemGroupLabel>
|
||||
<SelectItem v-for="item in employerCollection.items" :key="item.label" :item="item">
|
||||
<SelectItemText>{{ item.label }}</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectItemGroup>
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
<FieldError v-if="fieldErrors['applicant.employer_name']">
|
||||
{{ fieldErrors['applicant.employer_name'] }}
|
||||
</FieldError>
|
||||
@@ -653,7 +739,7 @@ function stepLabelClass(stepId: number) {
|
||||
</Field>
|
||||
<Field class="col-span-12">
|
||||
<FieldLabel for="employer_address">Alamat Majikan</FieldLabel>
|
||||
<Textarea id="employer_address" v-model="form.applicant.employer_address" rows="3" />
|
||||
<Textarea id="employer_address" v-model="form.applicant.employer_address" rows="3" disabled />
|
||||
<FieldError v-if="fieldErrors['applicant.employer_address']">
|
||||
{{ fieldErrors['applicant.employer_address'] }}
|
||||
</FieldError>
|
||||
@@ -666,17 +752,27 @@ function stepLabelClass(stepId: number) {
|
||||
</FieldError>
|
||||
</Field>
|
||||
<Field class="col-span-12 sm:col-span-4">
|
||||
<FieldLabel for="stock_monthly_contribution">Caruman Saham (RM)</FieldLabel>
|
||||
<FieldLabel for="stock_monthly_contribution">Potongan Modal Syer Minima</FieldLabel>
|
||||
<FieldDescription>
|
||||
Minima RM{{ MIN_STOCK_MONTHLY_CONTRIBUTION }} setiap bulan.
|
||||
</FieldDescription>
|
||||
<Input id="stock_monthly_contribution" v-model="form.applicant.stock_monthly_contribution" type="number"
|
||||
min="0" step="0.01" />
|
||||
:min="MIN_STOCK_MONTHLY_CONTRIBUTION" step="0.01" />
|
||||
<FieldDescription class="mt-2">
|
||||
Potongan RM{{ INITIAL_MANDATORY_STOCK_MONTHLY_CONTRIBUTION }} setiap bulan adalah wajib bagi 6 bulan
|
||||
pertama bagi menjelaskan modal syer minimum RM500. Anda boleh memilih potongan lebih tinggi. Selepas
|
||||
RM500 dijelaskan, potongan boleh dikekalkan atau dikurangkan sehingga minima
|
||||
RM{{ MIN_STOCK_MONTHLY_CONTRIBUTION }} setiap bulan.
|
||||
</FieldDescription>
|
||||
<FieldError v-if="fieldErrors['applicant.stock_monthly_contribution']">
|
||||
{{ fieldErrors['applicant.stock_monthly_contribution'] }}
|
||||
</FieldError>
|
||||
</Field>
|
||||
<Field class="col-span-12 sm:col-span-4">
|
||||
<FieldLabel for="fee_monthly_contribution">Caruman Yuran (RM)</FieldLabel>
|
||||
<FieldLabel for="fee_monthly_contribution">Potongan Yuran Bulanan</FieldLabel>
|
||||
<FieldDescription>Minima RM{{ MIN_FEE_MONTHLY_CONTRIBUTION }} setiap bulan.</FieldDescription>
|
||||
<Input id="fee_monthly_contribution" v-model="form.applicant.fee_monthly_contribution" type="number"
|
||||
min="0" step="0.01" />
|
||||
:min="MIN_FEE_MONTHLY_CONTRIBUTION" step="0.01" />
|
||||
<FieldError v-if="fieldErrors['applicant.fee_monthly_contribution']">
|
||||
{{ fieldErrors['applicant.fee_monthly_contribution'] }}
|
||||
</FieldError>
|
||||
@@ -688,7 +784,7 @@ function stepLabelClass(stepId: number) {
|
||||
<div v-for="(heir, index) in form.heirs" :key="index"
|
||||
class="rounded-lg border border-foreground/10 p-4">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div class="font-medium">Waris {{ index + 1 }}</div>
|
||||
<div class="font-medium">Penama</div>
|
||||
<Button v-if="form.heirs.length > 1" type="button" look="outline" size="sm"
|
||||
@click="removeHeir(index)">
|
||||
Buang
|
||||
@@ -742,10 +838,11 @@ function stepLabelClass(stepId: number) {
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="button" look="outline" @click="addHeir">
|
||||
<!-- Penama hanya boleh 1 orang (disable in frontend)-->
|
||||
<!-- <Button type="button" look="outline" @click="addHeir">
|
||||
<Lucide icon="Plus" class="mr-2 size-4" />
|
||||
Tambah Waris
|
||||
</Button>
|
||||
Tambah Penama
|
||||
</Button> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -806,7 +903,7 @@ function stepLabelClass(stepId: number) {
|
||||
</FieldError>
|
||||
</Field>
|
||||
<Field class="col-span-12 sm:col-span-6">
|
||||
<FieldLabel for="employer_letter">Surat Pengesahan Majikan (Pilihan)</FieldLabel>
|
||||
<FieldLabel for="employer_letter">Surat Pengesahan Majikan *</FieldLabel>
|
||||
<Input id="employer_letter" type="file" accept=".pdf,.jpg,.jpeg,.png"
|
||||
@change="handleFileChange('employer_letter', $event)" />
|
||||
<FieldError v-if="fieldErrors['documents.employer_letter']">{{ fieldErrors['documents.employer_letter']
|
||||
@@ -820,7 +917,7 @@ function stepLabelClass(stepId: number) {
|
||||
<div><span class="opacity-70">Emel:</span> {{ form.applicant.email }}</div>
|
||||
<div><span class="opacity-70">No. KP:</span> {{ form.applicant.ic_number }}</div>
|
||||
<div><span class="opacity-70">Majikan:</span> {{ form.applicant.employer_name }}</div>
|
||||
<div><span class="opacity-70">Bil. Waris:</span> {{ form.heirs.length }}</div>
|
||||
<div><span class="opacity-70">Bil. Penama:</span> {{ form.heirs.length }}</div>
|
||||
<div>
|
||||
<span class="opacity-70">Pencadang:</span>
|
||||
{{ form.references.proposer.name || '-' }}
|
||||
|
||||
Reference in New Issue
Block a user