DONE: fix naming, add transaction for completion of membership application (#12)
Build Docker Image / build-backend (push) Successful in 46s
Build Docker Image / build-frontend (push) Successful in 19s

Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #12
This commit was merged in pull request #12.
This commit is contained in:
2026-07-15 11:35:45 +08:00
parent f7429576b9
commit 59b571d944
14 changed files with 125 additions and 256 deletions
+39 -141
View File
@@ -32,6 +32,7 @@ import {
updateUserEmployment,
} from '../services/userEmployment.service'
import { getUser, updateUser } from '../services/user.service'
import { EMPLOYERS } from '@/constants/employers'
type SelectOption = { label: string; value: string }
@@ -62,35 +63,10 @@ const MEMBER_TYPE_OPTIONS: SelectOption[] = [
const EMPLOYMENT_TYPE_OPTIONS: SelectOption[] = [
{ label: 'Tetap', value: 'Permanent' },
{ label: 'Kontrak', value: 'Contract' },
{ label: 'Latihan Industri', value: 'Internship' },
{ label: 'Freelance', value: 'Freelance' },
{ label: 'Percubaan', value: 'Trial' },
{ label: 'Sambilan', value: 'Part-time' },
]
const EMPLOYERS = [
{
name: 'Infra Quest Sdn. Bhd. (IQSB)',
address: 'Lot 1045, Jalan Dato Lundang, 15200 Kota Bharu, Kelantan',
},
{
name: 'Permodalan Kelantan Berhad (PKB)',
address:
'Permodalan Kelantan Berhad, Tingkat 4, Wisma Permodalan Kelantan Berhad, Jalan Maju, 15000 Kota Bharu Kelantan',
},
{
name: 'Koperasi Permodalan Kelantan Berhad (KOPKB)',
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.',
},
{
name: 'Kel Infra Sdn. Bhd.',
address: 'Tingkat 2 Menara Perbadanan, Jalan Tengku Petra Semerak, 15000 Kota Bharu, Kelantan.',
},
] as const
const COMPANY_OPTIONS: SelectOption[] = EMPLOYERS.map((employer) => ({
label: employer.name,
value: employer.name,
@@ -747,13 +723,7 @@ onMounted(() => {
<Field v-if="isInactiveStatus">
<FieldLabel for="user-leave-date">Tarikh Berhenti Menjadi Anggota</FieldLabel>
<Input
id="user-leave-date"
v-model="form.leave_date"
class="w-full"
type="date"
:disabled="formDisabled"
/>
<Input id="user-leave-date" v-model="form.leave_date" class="w-full" type="date" :disabled="formDisabled" />
</Field>
<Field>
@@ -786,11 +756,8 @@ onMounted(() => {
<div v-if="loading" class="text-sm text-slate-500">Memuatkan pekerjaan...</div>
<div v-else-if="employments.length" class="space-y-3">
<div
v-for="employment in employments"
:key="employment.id"
class="flex flex-col gap-4 rounded-lg border border-foreground/10 p-4 sm:flex-row sm:items-start sm:justify-between"
>
<div v-for="employment in employments" :key="employment.id"
class="flex flex-col gap-4 rounded-lg border border-foreground/10 p-4 sm:flex-row sm:items-start sm:justify-between">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="font-medium text-slate-900">{{ employment.company_name }}</span>
@@ -804,46 +771,27 @@ onMounted(() => {
<p class="mt-1 text-sm text-slate-500">{{ formatSalary(employment.salary) }}</p>
</div>
<div class="flex shrink-0 gap-2">
<Button
type="button"
variant="ghost"
class="border border-foreground/15 shadow-none"
:disabled="deletingEmploymentId === employment.id || saving"
@click="startEditEmployment(employment)"
>
<Button type="button" variant="ghost" class="border border-foreground/15 shadow-none"
:disabled="deletingEmploymentId === employment.id || saving" @click="startEditEmployment(employment)">
<Lucide class="mr-2 size-4" icon="Pencil" />
Kemaskini
</Button>
<Button
type="button"
variant="ghost"
class="border border-foreground/15 shadow-none text-danger"
:disabled="deletingEmploymentId === employment.id || saving"
@click="onDeleteEmployment(employment)"
>
<Lucide
class="mr-2 size-4"
:icon="deletingEmploymentId === employment.id ? 'LoaderCircle' : 'Trash'"
:class="{ 'animate-spin': deletingEmploymentId === employment.id }"
/>
<Button type="button" variant="ghost" class="border border-foreground/15 shadow-none text-danger"
:disabled="deletingEmploymentId === employment.id || saving" @click="onDeleteEmployment(employment)">
<Lucide class="mr-2 size-4" :icon="deletingEmploymentId === employment.id ? 'LoaderCircle' : 'Trash'"
:class="{ 'animate-spin': deletingEmploymentId === employment.id }" />
Padam
</Button>
</div>
</div>
</div>
<div
v-else
class="rounded-lg border border-dashed border-foreground/15 p-6 text-center text-sm text-slate-500"
>
<div v-else class="rounded-lg border border-dashed border-foreground/15 p-6 text-center text-sm text-slate-500">
Tiada pekerjaan direkodkan.
</div>
<form
v-if="showEmploymentForm"
class="space-y-6 border-t border-foreground/10 pt-6"
@submit.prevent="onSaveEmployment"
>
<form v-if="showEmploymentForm" class="space-y-6 border-t border-foreground/10 pt-6"
@submit.prevent="onSaveEmployment">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h4 class="text-base font-semibold text-slate-900">
@@ -858,14 +806,9 @@ onMounted(() => {
</p>
</div>
<div class="flex gap-2">
<Button
v-if="isEditingEmployment"
type="button"
variant="ghost"
class="border border-foreground/15 shadow-none"
:disabled="savingEmployment"
@click="resetEmploymentForm"
>
<Button v-if="isEditingEmployment" type="button" variant="ghost"
class="border border-foreground/15 shadow-none" :disabled="savingEmployment"
@click="resetEmploymentForm">
Batal
</Button>
<Button type="submit" variant="primary" :disabled="savingEmployment || saving">
@@ -878,14 +821,9 @@ onMounted(() => {
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<Field>
<FieldLabel>Nama Syarikat</FieldLabel>
<SelectRoot
:key="`company-name-${editingEmploymentId ?? 'new'}`"
class="w-full"
:collection="companyNameCollection"
:default-value="companyNameInitial"
:disabled="savingEmployment || saving"
@value-change="setCompanyNameValue"
>
<SelectRoot :key="`company-name-${editingEmploymentId ?? 'new'}`" class="w-full"
:collection="companyNameCollection" :default-value="companyNameInitial"
:disabled="savingEmployment || saving" @value-change="setCompanyNameValue">
<SelectControl>
<SelectTrigger :aria-invalid="!!employmentErrors.company_name">
<SelectValueText placeholder="Pilih syarikat" />
@@ -894,11 +832,7 @@ onMounted(() => {
<SelectContent>
<SelectItemGroup>
<SelectItemGroupLabel>Nama Syarikat</SelectItemGroupLabel>
<SelectItem
v-for="item in companyNameCollection.items"
:key="item.label"
:item="item"
>
<SelectItem v-for="item in companyNameCollection.items" :key="item.label" :item="item">
<SelectItemText>{{ item.label }}</SelectItemText>
</SelectItem>
</SelectItemGroup>
@@ -911,15 +845,9 @@ onMounted(() => {
<Field>
<FieldLabel for="employment-job-title">Jawatan</FieldLabel>
<Input
id="employment-job-title"
v-model="employmentForm.job_title"
type="text"
placeholder="Jawatan"
:disabled="savingEmployment || saving"
:aria-invalid="!!employmentErrors.job_title"
@input="clearEmploymentFieldError('job_title')"
/>
<Input id="employment-job-title" v-model="employmentForm.job_title" type="text" placeholder="Jawatan"
:disabled="savingEmployment || saving" :aria-invalid="!!employmentErrors.job_title"
@input="clearEmploymentFieldError('job_title')" />
<FieldError v-if="employmentErrors.job_title">
{{ employmentErrors.job_title }}
</FieldError>
@@ -927,14 +855,9 @@ onMounted(() => {
<Field>
<FieldLabel>Jenis Kerja</FieldLabel>
<SelectRoot
:key="`employment-type-${editingEmploymentId ?? 'new'}`"
class="w-full"
:collection="employmentTypeCollection"
:default-value="employmentTypeInitial"
:disabled="savingEmployment || saving"
@value-change="setEmploymentTypeValue"
>
<SelectRoot :key="`employment-type-${editingEmploymentId ?? 'new'}`" class="w-full"
:collection="employmentTypeCollection" :default-value="employmentTypeInitial"
:disabled="savingEmployment || saving" @value-change="setEmploymentTypeValue">
<SelectControl>
<SelectTrigger :aria-invalid="!!employmentErrors.employment_type">
<SelectValueText placeholder="Pilih jenis kerja" />
@@ -943,11 +866,7 @@ onMounted(() => {
<SelectContent>
<SelectItemGroup>
<SelectItemGroupLabel>Jenis Kerja</SelectItemGroupLabel>
<SelectItem
v-for="item in employmentTypeCollection.items"
:key="item.label"
:item="item"
>
<SelectItem v-for="item in employmentTypeCollection.items" :key="item.label" :item="item">
<SelectItemText>{{ item.label }}</SelectItemText>
</SelectItem>
</SelectItemGroup>
@@ -960,30 +879,17 @@ onMounted(() => {
<Field>
<FieldLabel for="employment-salary">Gaji (RM)</FieldLabel>
<Input
id="employment-salary"
v-model="employmentForm.salary"
type="number"
min="0"
step="0.01"
placeholder="0.00"
:disabled="savingEmployment || saving"
:aria-invalid="!!employmentErrors.salary"
@input="clearEmploymentFieldError('salary')"
/>
<Input id="employment-salary" v-model="employmentForm.salary" type="number" min="0" step="0.01"
placeholder="0.00" :disabled="savingEmployment || saving" :aria-invalid="!!employmentErrors.salary"
@input="clearEmploymentFieldError('salary')" />
<FieldError v-if="employmentErrors.salary">{{ employmentErrors.salary }}</FieldError>
</Field>
<Field>
<FieldLabel for="employment-start-date">Tarikh Mula</FieldLabel>
<Input
id="employment-start-date"
v-model="employmentForm.start_date"
type="date"
:disabled="savingEmployment || saving"
:aria-invalid="!!employmentErrors.start_date"
@input="clearEmploymentFieldError('start_date')"
/>
<Input id="employment-start-date" v-model="employmentForm.start_date" type="date"
:disabled="savingEmployment || saving" :aria-invalid="!!employmentErrors.start_date"
@input="clearEmploymentFieldError('start_date')" />
<FieldError v-if="employmentErrors.start_date">
{{ employmentErrors.start_date }}
</FieldError>
@@ -991,25 +897,17 @@ onMounted(() => {
<Field>
<FieldLabel for="employment-end-date">Tarikh Tamat</FieldLabel>
<Input
id="employment-end-date"
v-model="employmentForm.end_date"
type="date"
<Input id="employment-end-date" v-model="employmentForm.end_date" type="date"
:disabled="employmentForm.is_current || savingEmployment || saving"
:aria-invalid="!!employmentErrors.end_date"
@input="clearEmploymentFieldError('end_date')"
/>
:aria-invalid="!!employmentErrors.end_date" @input="clearEmploymentFieldError('end_date')" />
<FieldError v-if="employmentErrors.end_date">
{{ employmentErrors.end_date }}
</FieldError>
</Field>
<Field class="md:col-span-2">
<CheckboxRoot
:checked="employmentForm.is_current"
:disabled="savingEmployment || saving"
@checked-change="({ checked }) => (employmentForm.is_current = checked === true)"
>
<CheckboxRoot :checked="employmentForm.is_current" :disabled="savingEmployment || saving"
@checked-change="({ checked }) => (employmentForm.is_current = checked === true)">
<CheckboxControl />
<CheckboxLabel>Pekerjaan semasa</CheckboxLabel>
</CheckboxRoot>