Done permohonan temujanji with BE

This commit is contained in:
Md Afiq Iskandar 2024-09-04 19:18:22 +08:00
parent 254b6334af
commit 7b1e5bbc13
11 changed files with 780 additions and 438 deletions

View File

@ -782,7 +782,24 @@ watch(
</div>
</div>
</div>
<div v-else class="table-wrapper p-4 text-center">
<p class="text-[rgb(var(--text-color))]">No data found</p>
<div v-else class="table-wrapper p-4">
<div
class="border border-[rgb(var(--border-color))] rounded-lg overflow-hidden"
>
<div
class="bg-[rgb(var(--bg-2))] p-4 border-b border-[rgb(var(--border-color))]"
>
<h3 class="text-lg font-semibold text-[rgb(var(--text-color))]"></h3>
</div>
<div class="p-8 text-center">
<Icon name="mdi:table-off" class="text-gray-300 mb-4" size="48px" />
<p class="text-[rgb(var(--text-color))] text-lg font-medium">
Tiada data
</p>
<p class="text-gray-500 mt-2">
Tiada entri untuk dipaparkan pada masa ini.
</p>
</div>
</div>
</div>
</template>

View File

@ -81,6 +81,7 @@
/>
<!-- Barang Section -->
<div class="mb-4">
<h3 class="mb-2">Senarai Barang</h3>
<table
@ -97,7 +98,7 @@
<tbody>
<tr v-for="(barang, index) in barangList" :key="index">
<td class="border border-gray-300 p-2">
{{ barang.jenisBarang }}
{{ getJenisBarangLabel(barang.jenisBarangDetail) }}
</td>
<td class="border border-gray-300 p-2">
{{ barang.kuantitiBarang }}
@ -165,23 +166,11 @@
<!-- Action Buttons -->
<div class="flex justify-end gap-2 mt-4">
<rs-button type="button" @click="navigateBack" variant="danger"
>Kembali</rs-button
>
<rs-button
type="button"
btn-type="submit"
@click="simpan"
variant="primary"
<rs-button @click="navigateBack" variant="danger">Kembali</rs-button>
<rs-button @click.prevent="simpan" variant="primary"
>Simpan</rs-button
>
<rs-button
type="submit"
btn-type="submit"
:disabled="!isFormValid"
variant="success"
>Hantar</rs-button
>
<rs-button btn-type="submit" variant="success">Hantar</rs-button>
</div>
</FormKit>
</rs-card>
@ -203,10 +192,11 @@
#default="{ state: formState }"
>
<FormKit
type="text"
name="jenisBarang"
type="select"
name="jenisBarangDetail"
label="Jenis Barang"
v-model="currentBarang.jenisBarang"
v-model="currentBarang.jenisBarangDetail"
:options="jenisBarangDetailOptions"
validation="required"
:validation-messages="{
required: 'Jenis Barang diperlukan',
@ -248,18 +238,6 @@
}"
/>
<FormKit
type="select"
name="jenisBarangDetail"
label="Jenis Barang Detail"
v-model="currentBarang.jenisBarangDetail"
:options="jenisBarangDetailOptions"
validation="required"
:validation-messages="{
required: 'Jenis Barang Detail diperlukan',
}"
/>
<FormKit
type="select"
name="jenisBarangSiber"
@ -295,9 +273,7 @@
</template>
<script setup>
import { ref, watch } from "vue";
import { useRouter } from "vue-router";
const { $swal } = useNuxtApp();
const router = useRouter();
const namaPemohon = ref("");
@ -317,44 +293,50 @@ const slotMasa = ref("");
// State for single checkbox
const isPenghantarSameAsPemohon = ref(false);
const isBarangModalOpen = ref(false);
const editingBarangIndex = ref(null);
const currentBarang = ref({
jenisBarang: "",
tandaBarang: "",
keadaanBarang: "",
kuantitiBarang: 1,
jenisBarangDetail: "",
jenisBarangSiber: "",
});
const jenisBarangDetailOptions = [
"PASPORT",
"MALPASS",
"CAP KESELAMATAN",
"CAP JARI",
"PEMERIKSAAN",
"I-KAD",
"LAIN-LAIN",
];
const jenisBarangSiberOptions = ["SIBER", "TULISAN TANGAN"];
// Watcher to update Penghantar fields when the single checkbox is checked
watch(isPenghantarSameAsPemohon, (newValue) => {
if (newValue) {
// Copy values from Pemohon fields to Penghantar fields
namaPenghantar.value = namaPemohon.value;
pangkatPenghantar.value = pangkatPemohon.value;
noPegawaiPenghantar.value = noPegawaiPemohon.value;
} else {
// Clear Penghantar fields when unchecked
namaPenghantar.value = "";
pangkatPenghantar.value = "";
noPegawaiPenghantar.value = "";
}
});
const isBarangModalOpen = ref(false);
const editingBarangIndex = ref(null);
const currentBarang = ref({
jenisBarangDetail: "",
tandaBarang: "",
keadaanBarang: "",
kuantitiBarang: 1,
jenisBarangSiber: "",
});
const jenisBarangDetailOptions = ref([]);
const jenisBarangSiberOptions = ref([]);
// Fetch lookup data from API
const fetchLookupData = async (type) => {
try {
const response = await $fetch(`/api/lookup?type=${type}`);
if (response.statusCode === 200) {
// Data return with value and label
return response.data;
}
} catch (error) {
console.error(`Error fetching ${type} lookup data:`, error);
return [];
}
};
onMounted(async () => {
jenisBarangDetailOptions.value = await fetchLookupData("jenis_barang");
jenisBarangSiberOptions.value = await fetchLookupData("jenis_barang_siber");
});
const navigateBack = () => {
router.back();
};
@ -362,11 +344,10 @@ const navigateBack = () => {
const openBarangModal = () => {
editingBarangIndex.value = null;
currentBarang.value = {
jenisBarang: "",
jenisBarangDetail: "",
tandaBarang: "",
keadaanBarang: "",
kuantitiBarang: 1,
jenisBarangDetail: "",
jenisBarangSiber: "",
};
isBarangModalOpen.value = true;
@ -415,15 +396,11 @@ const isFormValid = () => {
(field) => field.value !== "" && field.value !== 0
);
const areBarangFieldsValid = barangList.value.every((barang) =>
Object.values(barang).every((value) => value !== "" && value !== 0)
);
// const areBarangFieldsValid = barangList.value.every((barang) =>
// Object.values(barang).every((value) => value !== "" && value !== 0)
// );
return (
areRequiredFieldsFilled &&
areBarangFieldsValid &&
barangList.value.length > 0
);
return areRequiredFieldsFilled && barangList.value.length > 0;
};
const simpan = async () => {
@ -446,17 +423,12 @@ const submitData = async (isDraft) => {
namaPemohon: namaPemohon.value,
pangkatPemohon: pangkatPemohon.value,
noPegawaiPemohon: noPegawaiPemohon.value,
namaPenghantar: isPenghantarSameAsPemohon.value
? null
: namaPenghantar.value,
pangkatPenghantar: isPenghantarSameAsPemohon.value
? null
: pangkatPenghantar.value,
noPegawaiPenghantar: isPenghantarSameAsPemohon.value
? null
: noPegawaiPenghantar.value,
namaPenghantar: namaPenghantar.value,
pangkatPenghantar: pangkatPenghantar.value,
noPegawaiPenghantar: noPegawaiPenghantar.value,
isPenghantarSameAsPemohon: isPenghantarSameAsPemohon.value,
ringkasanKenyataanKes: ringkasanKenyataanKes.value,
bilangan: bilangan.value,
bilangan: bilangan.value.toString(),
barangList: barangList.value,
noKertasSiasatan: noKertasSiasatan.value,
noLaporanPolis: noLaporanPolis.value,
@ -466,16 +438,25 @@ const submitData = async (isDraft) => {
},
});
$swal.fire({
title: "Berjaya!",
text: response.message,
icon: "success",
confirmButtonText: "OK",
});
// Redirect user based on action (draft or submission)
if (!isDraft) {
router.push("/permohonan-temujanji/success");
if (response.statusCode === 200) {
await $swal.fire({
title: "Berjaya!",
text: response.message,
icon: "success",
confirmButtonText: "OK",
});
// Redirect to senarai page after successful submission
if (!isDraft) {
router.push('/permohonan-temujanji/senarai');
}
} else {
$swal.fire({
title: "Ralat!",
text: response.message,
icon: "error",
confirmButtonText: "OK",
});
}
} catch (error) {
$swal.fire({
@ -495,7 +476,12 @@ const submitData = async (isDraft) => {
}
};
const { $swal } = useNuxtApp();
const getJenisBarangLabel = (value) => {
const option = jenisBarangDetailOptions.value.find(
(opt) => opt.__original === value
);
return option ? option.label : value;
};
</script>
<style lang="scss" scoped>

View File

@ -1,7 +1,7 @@
<template>
<div>
<div class="flex justify-between items-center">
<h1>Permohonan Baru</h1>
<h1>Kemaskini Permohonan</h1>
</div>
<rs-card class="mt-4 p-4">
@ -97,7 +97,7 @@
<tbody>
<tr v-for="(barang, index) in barangList" :key="index">
<td class="border border-gray-300 p-2">
{{ barang.jenisBarang }}
{{ getJenisBarangLabel(barang.jenisBarangDetail) }}
</td>
<td class="border border-gray-300 p-2">
{{ barang.kuantitiBarang }}
@ -160,23 +160,11 @@
<!-- Action Buttons -->
<div class="flex justify-end gap-2 mt-4">
<rs-button type="button" @click="navigateBack" variant="danger"
>Kembali</rs-button
>
<rs-button
type="button"
@click="simpan"
btn-type="submit"
variant="primary"
>Simpan</rs-button
>
<rs-button
type="submit"
@click="submitForm"
btn-type="submit"
variant="success"
>Hantar</rs-button
<rs-button @click="navigateBack" variant="danger">Kembali</rs-button>
<rs-button @click.prevent="simpan" variant="primary"
>Kemaskini</rs-button
>
<rs-button btn-type="submit" variant="success">Hantar</rs-button>
</div>
</FormKit>
</rs-card>
@ -198,10 +186,11 @@
#default="{ state: formState }"
>
<FormKit
type="text"
name="jenisBarang"
type="select"
name="jenisBarangDetail"
label="Jenis Barang"
v-model="currentBarang.jenisBarang"
v-model="currentBarang.jenisBarangDetail"
:options="jenisBarangDetailOptions"
validation="required"
:validation-messages="{
required: 'Jenis Barang diperlukan',
@ -243,18 +232,6 @@
}"
/>
<FormKit
type="select"
name="jenisBarangDetail"
label="Jenis Barang Detail"
v-model="currentBarang.jenisBarangDetail"
:options="jenisBarangDetailOptions"
validation="required"
:validation-messages="{
required: 'Jenis Barang Detail diperlukan',
}"
/>
<FormKit
type="select"
name="jenisBarangSiber"
@ -290,15 +267,13 @@
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import { useRouter, useRoute } from "vue-router";
const { $swal } = useNuxtApp();
const router = useRouter();
const route = useRoute();
const noSiri = ref(route.params.noSiri);
// Form data
// Form data refs
const namaPemohon = ref("");
const pangkatPemohon = ref("");
const noPegawaiPemohon = ref("");
@ -316,58 +291,100 @@ const slotMasa = ref("");
// State for single checkbox
const isPenghantarSameAsPemohon = ref(false);
const jenisBarangDetailOptions = [
"PASPORT",
"MALPASS",
"CAP KESELAMATAN",
"CAP JARI",
"PEMERIKSAAN",
"I-KAD",
"LAIN-LAIN",
];
const jenisBarangSiberOptions = ["SIBER", "TULISAN TANGAN"];
// Barang modal state
const isBarangModalOpen = ref(false);
const editingBarangIndex = ref(null);
const currentBarang = ref({
jenisBarang: "",
tandaBarang: "",
keadaanBarang: "",
kuantitiBarang: 1,
jenisBarangDetail: "",
jenisBarangSiber: "",
});
// Watcher to update Penghantar fields when the checkbox is checked
// Remove computed properties and add watch
watch(isPenghantarSameAsPemohon, (newValue) => {
if (newValue) {
// Copy values from Pemohon fields to Penghantar fields
namaPenghantar.value = namaPemohon.value;
pangkatPenghantar.value = pangkatPemohon.value;
noPegawaiPenghantar.value = noPegawaiPemohon.value;
} else {
// Clear Penghantar fields when unchecked
namaPenghantar.value = "";
pangkatPenghantar.value = "";
noPegawaiPenghantar.value = "";
}
});
const navigateBack = () => {
router.back();
// Update these to be reactive refs
const jenisBarangDetailOptions = ref([]);
const jenisBarangSiberOptions = ref([]);
// Fetch lookup data from API
const fetchLookupData = async (type) => {
try {
const response = await $fetch(`/api/lookup?type=${type}`);
if (response.statusCode === 200) {
// Data return with value and label
return response.data;
}
} catch (error) {
console.error(`Error fetching ${type} lookup data:`, error);
return [];
}
};
// Fetch existing data
const fetchExistingData = async (noSiri) => {
try {
const response = await $fetch(`/api/permohonan/${noSiri}`);
if (response.statusCode === 200) {
return response.data;
}
} catch (error) {
console.error("Error fetching existing data:", error);
$swal.fire({
title: "Ralat!",
text: "Gagal mendapatkan data permohonan.",
icon: "error",
confirmButtonText: "OK",
});
}
};
onMounted(async () => {
const existingData = await fetchExistingData(noSiri.value);
if (existingData) {
// Set the form values based on the existingData
namaPemohon.value = existingData.namaPemohon;
pangkatPemohon.value = existingData.pangkatPemohon;
noPegawaiPemohon.value = existingData.noPegawaiPemohon;
namaPenghantar.value = existingData.namaPenghantar;
pangkatPenghantar.value = existingData.pangkatPenghantar;
noPegawaiPenghantar.value = existingData.noPegawaiPenghantar;
ringkasanKenyataanKes.value = existingData.ringkasanKenyataanKes;
bilangan.value = existingData.bilangan;
barangList.value = existingData.barangList;
noKertasSiasatan.value = existingData.noKertasSiasatan;
noLaporanPolis.value = existingData.noLaporanPolis;
tarikhTemujanji.value = existingData.tarikhTemujanji;
slotMasa.value = existingData.slotMasa;
isPenghantarSameAsPemohon.value = existingData.isPenghantarSameAsPemohon;
}
// Fetch lookup data
jenisBarangDetailOptions.value = await fetchLookupData("jenis_barang");
jenisBarangSiberOptions.value = await fetchLookupData("jenis_barang_siber");
});
// Barang modal state
const isBarangModalOpen = ref(false);
const editingBarangIndex = ref(null);
const currentBarang = ref({
jenisBarangDetail: "",
tandaBarang: "",
keadaanBarang: "",
kuantitiBarang: 1,
jenisBarangSiber: "",
});
// Barang modal functions
const openBarangModal = () => {
editingBarangIndex.value = null;
currentBarang.value = {
jenisBarang: "",
jenisBarangDetail: "",
tandaBarang: "",
keadaanBarang: "",
kuantitiBarang: 1,
jenisBarangDetail: "",
jenisBarangSiber: "",
};
isBarangModalOpen.value = true;
@ -396,7 +413,10 @@ const saveBarangModal = () => {
isBarangModalOpen.value = false;
};
// Validate the form
const navigateBack = () => {
router.back();
};
const isFormValid = () => {
const requiredFields = [
namaPemohon,
@ -417,179 +437,88 @@ const isFormValid = () => {
(field) => field.value !== "" && field.value !== 0
);
const areBarangFieldsValid = barangList.value.every((barang) =>
Object.values(barang).every((value) => value !== "" && value !== 0)
);
// const areBarangFieldsValid = barangList.value.every((barang) =>
// Object.values(barang).every((value) => value !== "" && value !== 0)
// );
return (
areRequiredFieldsFilled &&
areBarangFieldsValid &&
barangList.value.length > 0
);
return areRequiredFieldsFilled && barangList.value.length > 0;
};
const simpan = () => {
$swal
.fire({
title: "Adakah anda pasti?",
text: "Borang boleh dikemas kini selepas di simpan.",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Ya, Hantar",
cancelButtonText: "Batal",
})
.then((result) => {
if (result.isConfirmed) {
// Handle form submission
console.log({
const simpan = async () => {
const isDraft = true; // Simpan means saving as draft
await submitData(isDraft);
};
const submitForm = async () => {
const isDraft = false; // Submit means final submission
await submitData(isDraft);
};
const submitData = async (isDraft) => {
if (isFormValid()) {
try {
const response = await $fetch(`/api/permohonan/${noSiri.value}`, {
method: "PUT",
body: {
namaPemohon: namaPemohon.value,
pangkatPemohon: pangkatPemohon.value,
noPegawaiPemohon: noPegawaiPemohon.value,
namaPenghantar: namaPenghantar.value,
pangkatPenghantar: pangkatPenghantar.value,
noPegawaiPenghantar: noPegawaiPenghantar.value,
isPenghantarSameAsPemohon: isPenghantarSameAsPemohon.value,
ringkasanKenyataanKes: ringkasanKenyataanKes.value,
bilangan: bilangan.value,
bilangan: bilangan.value.toString(),
barangList: barangList.value,
noKertasSiasatan: noKertasSiasatan.value,
noLaporanPolis: noLaporanPolis.value,
tarikhTemujanji: tarikhTemujanji.value,
slotMasa: slotMasa.value,
});
isDraft: isDraft,
},
});
// Show success message
$swal.fire({
if (response.statusCode === 200) {
await $swal.fire({
title: "Berjaya!",
text: "Borang telah berjaya disimpan.",
text: isDraft
? "Permohonan telah berjaya disimpan sebagai draf."
: "Permohonan telah berjaya dikemaskini.",
icon: "success",
confirmButtonText: "OK",
});
}
});
};
const submitForm = () => {
$swal
.fire({
title: "Adakah anda pasti?",
text: "Borang boleh dikemaskini selepas di simpan.",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Ya, Hantar",
cancelButtonText: "Batal",
})
.then((result) => {
if (result.isConfirmed) {
if (isFormValid()) {
// Handle form submission
console.log({
namaPemohon: namaPemohon.value,
pangkatPemohon: pangkatPemohon.value,
noPegawaiPemohon: noPegawaiPemohon.value,
namaPenghantar: namaPenghantar.value,
pangkatPenghantar: pangkatPenghantar.value,
noPegawaiPenghantar: noPegawaiPenghantar.value,
ringkasanKenyataanKes: ringkasanKenyataanKes.value,
bilangan: bilangan.value,
barangList: barangList.value,
noKertasSiasatan: noKertasSiasatan.value,
noLaporanPolis: noLaporanPolis.value,
tarikhTemujanji: tarikhTemujanji.value,
slotMasa: slotMasa.value,
});
// Show success message
$swal.fire({
title: "Berjaya!",
text: "Borang telah berjaya dihantar.",
icon: "success",
confirmButtonText: "OK",
});
} else {
// Show error message
$swal.fire({
title: "Ralat!",
text: "Sila isi semua medan yang diperlukan dan tambah sekurang-kurangnya satu barang.",
icon: "error",
confirmButtonText: "OK",
});
// Redirect to senarai page after successful submission
if (!isDraft) {
router.push("/permohonan-temujanji/senarai");
}
} else {
throw new Error(response.message);
}
} catch (error) {
$swal.fire({
title: "Ralat!",
text: error.message || "Gagal mengemaskini permohonan. Sila cuba lagi.",
icon: "error",
confirmButtonText: "OK",
});
}
} else {
$swal.fire({
title: "Ralat!",
text: "Sila isi semua medan yang diperlukan dan tambah sekurang-kurangnya satu barang.",
icon: "error",
confirmButtonText: "OK",
});
}
};
// Function to generate sample data
function generateSampleData(noSiri) {
const randomNumber = (min, max) =>
Math.floor(Math.random() * (max - min + 1) + min);
const randomChoice = (arr) => arr[Math.floor(Math.random() * arr.length)];
const namaPemohon = [
"Ali bin Abu",
"Siti binti Ahmad",
"Muthu a/l Rajan",
"Lim Wei Ling",
];
const pangkat = ["Inspektor", "Sarjan", "Koperal", "Konstabel"];
const jenisBarangOptions = [
"Dokumen",
"Peralatan Elektronik",
"Senjata",
"Dadah",
"Barang Kemas",
];
const keadaanBarangOptions = ["Baik", "Sederhana", "Rosak"];
const generateBarang = () => ({
jenisBarang: randomChoice(jenisBarangOptions),
tandaBarang: `TB-${randomNumber(1000, 9999)}`,
keadaanBarang: randomChoice(keadaanBarangOptions),
kuantitiBarang: randomNumber(1, 10),
jenisBarangDetail: randomChoice(jenisBarangDetailOptions),
jenisBarangSiber: randomChoice(jenisBarangSiberOptions),
});
return {
noSiri: noSiri,
namaPemohon: randomChoice(namaPemohon),
pangkatPemohon: randomChoice(pangkat),
noPegawaiPemohon: `PG${randomNumber(10000, 99999)}`,
namaPenghantar: randomChoice(namaPemohon),
pangkatPenghantar: randomChoice(pangkat),
noPegawaiPenghantar: `PG${randomNumber(10000, 99999)}`,
ringkasanKenyataanKes: `Kes ${noSiri}: Penemuan barang bukti dalam serbuan di lokasi ${randomChoice(
["A", "B", "C", "D"]
)}`,
bilangan: randomNumber(1, 5),
barangList: Array.from({ length: randomNumber(1, 3) }, generateBarang),
noKertasSiasatan: `KS-${randomNumber(10000, 99999)}`,
noLaporanPolis: `RPT-${randomNumber(100000, 999999)}`,
tarikhTemujanji: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
.toISOString()
.split("T")[0], // 7 days from now
slotMasa: `${randomNumber(9, 16)}:00`, // Random hour between 9 AM and 4 PM
};
}
onMounted(() => {
const sampleData = generateSampleData(noSiri.value);
namaPemohon.value = sampleData.namaPemohon;
pangkatPemohon.value = sampleData.pangkatPemohon;
noPegawaiPemohon.value = sampleData.noPegawaiPemohon;
namaPenghantar.value = sampleData.namaPenghantar;
pangkatPenghantar.value = sampleData.pangkatPenghantar;
noPegawaiPenghantar.value = sampleData.noPegawaiPenghantar;
ringkasanKenyataanKes.value = sampleData.ringkasanKenyataanKes;
bilangan.value = sampleData.bilangan;
barangList.value = sampleData.barangList;
noKertasSiasatan.value = sampleData.noKertasSiasatan;
noLaporanPolis.value = sampleData.noLaporanPolis;
tarikhTemujanji.value = sampleData.tarikhTemujanji;
slotMasa.value = sampleData.slotMasa;
});
const { $swal } = useNuxtApp();
const getJenisBarangLabel = (value) => {
const option = jenisBarangDetailOptions.value.find(
(opt) => opt.__original === value
);
return option ? option.label : value;
};
</script>
<style lang="scss" scoped>

View File

@ -60,7 +60,13 @@
<!-- Actions for each permohonan -->
<template v-slot:butiran="data">
<div class="flex flex-wrap gap-2" v-if="data.value.status !== 'Sah'">
<div
class="flex flex-wrap gap-2"
v-if="
data.value.status !== 'Sah' &&
data.value.status !== 'Permohonan Dihantar'
"
>
<!-- Button to navigate to the "Kemaskini" page for the selected permohonan -->
<rs-button
@click="kemaskini(data.value.noSiri)"
@ -84,9 +90,13 @@
</rs-button>
</div>
<!-- If permohonan has been confirmed -->
<!-- If permohonan has been confirmed or submitted -->
<div v-else>
<span>Permohonan telah disahkan</span>
<span>{{
data.value.status === "Sah"
? "Permohonan telah disahkan"
: "Permohonan telah dihantar"
}}</span>
</div>
</template>
</rs-table>
@ -140,7 +150,6 @@ const hapus = async (noSiri) => {
if (confirmation.isConfirmed) {
try {
// Call API to delete the permohonan
const response = await $fetch(`/api/permohonan/${noSiri}`, {
method: "DELETE",
});

View File

@ -89,18 +89,21 @@ model permohonan {
penghantarID Int?
status_permohonan String @db.VarChar(255)
ringkasan_kenyataan_kes String? @db.Text
bilangan String? @db.VarChar(255)
bilangan Int?
jenis_barang Int?
tanda_barang String? @db.VarChar(255)
keadaan_barang String? @db.VarChar(255)
kuantiti_barang Int?
jenis_barang_details String? @db.Text
no_laporan_polis String? @db.VarChar(255)
no_kertas_siasatan String? @db.VarChar(255)
tarikh_temujanji DateTime? @db.DateTime(0)
create_at DateTime @db.DateTime(0)
penghantar penghantar? @relation(fields: [penghantarID], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "permohonan_ibfk_1")
pemohon pemohon? @relation(fields: [pemohonID], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "permohonan_ibfk_2")
slot_masa DateTime? @db.Time(0)
create_at DateTime? @db.DateTime(0)
modified_at DateTime? @db.DateTime(0)
lookup lookup? @relation(fields: [jenis_barang], references: [lookupID], onDelete: NoAction, onUpdate: NoAction, map: "permohonan_ibfk_3")
pemohon pemohon? @relation(fields: [pemohonID], references: [id], onDelete: Cascade, onUpdate: Restrict, map: "permohonan_ibfk_2")
penghantar penghantar? @relation(fields: [penghantarID], references: [id], onDelete: Cascade, onUpdate: Restrict, map: "permohonan_ibfk_1")
permohonan_assign_forensik permohonan_assign_forensik[]
permohonan_jenis_barang permohonan_jenis_barang[]
permohonan_penerimaan permohonan_penerimaan[]
@ -279,10 +282,10 @@ model report {
dapatan Int?
create_at DateTime? @db.DateTime(0)
create_by Int
permohonan permohonan @relation(fields: [permohonanID], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "report_ibfk_1")
document document? @relation(fields: [gambarID], references: [documentID], onDelete: NoAction, onUpdate: NoAction, map: "report_ibfk_2")
lookup_report_dapatanTolookup lookup? @relation("report_dapatanTolookup", fields: [dapatan], references: [lookupID], onDelete: NoAction, onUpdate: NoAction, map: "report_ibfk_3")
lookup_report_jenis_barangTolookup lookup @relation("report_jenis_barangTolookup", fields: [jenis_barang], references: [lookupID], onDelete: NoAction, onUpdate: NoAction, map: "report_ibfk_4")
permohonan permohonan @relation(fields: [permohonanID], references: [id], onDelete: Cascade, onUpdate: Restrict, map: "report_ibfk_1")
report_doc_support report_doc_support[]
@@index([dapatan], map: "dapatan")

View File

@ -21,9 +21,18 @@ export default defineEventHandler(async (event) => {
},
});
// Transform the lookups data to the required format
const transformedLookups = [
{ label: "", value: null }, // Add an empty option as the first item
...lookups.map((lookup) => ({
label: lookup.lookupValue,
value: lookup.lookupID,
})),
];
return {
statusCode: 200,
data: lookups,
data: transformedLookups,
};
} catch (error) {
return {

View File

@ -0,0 +1,56 @@
export default defineEventHandler(async (event) => {
const { noSiri } = event.context.params; // Get the noSiri from the request parameters
try {
// Find the permohonan by its `no_siri`
const permohonan = await prisma.permohonan.findUnique({
where: { no_siri: noSiri },
});
if (!permohonan) {
return {
statusCode: 404,
message: `Permohonan with noSiri ${noSiri} not found.`,
};
}
// Delete pemohonan
await prisma.pemohon.delete({
where: {
id: permohonan.pemohonID,
},
});
// Delete penghantar
await prisma.penghantar.delete({
where: {
id: permohonan.penghantarID,
},
});
// Delete report
await prisma.report.deleteMany({
where: {
permohonanID: permohonan.id,
},
});
// Delete permohonan
await prisma.permohonan.delete({
where: {
id: permohonan.id,
},
});
return {
statusCode: 200,
message: "Permohonan and related data successfully deleted.",
};
} catch (error) {
console.error("Error deleting permohonan:", error);
return {
statusCode: 500,
message: "Failed to delete permohonan. Please try again.",
};
}
});

View File

@ -0,0 +1,77 @@
export default defineEventHandler(async (event) => {
// Get the `noSiri` from the request parameters
const { noSiri } = event.context.params;
try {
// Fetch the permohonan by `no_siri` from the Prisma database
const permohonan = await prisma.permohonan.findUnique({
where: {
no_siri: noSiri, // Unique identifier for permohonan
},
include: {
// Include related fields if necessary
pemohon: {
select: {
user: {
select: {
userFullName: true,
},
},
pangkat_pemohon: true,
no_pegawai_pemohon: true,
},
},
penghantar: true,
report: true, // Assuming 'report' is where the `barang` (items) are stored
},
});
// If no permohonan found, return a 404 response
if (!permohonan) {
return {
statusCode: 404,
message: `Permohonan with noSiri ${noSiri} not found`,
};
}
// Map and return the data as expected by the frontend
return {
statusCode: 200,
data: {
namaPemohon: permohonan.pemohon?.user?.userFullName || "", // Get namaPemohon from userID
pangkatPemohon: permohonan.pemohon?.pangkat_pemohon || "",
noPegawaiPemohon: permohonan.pemohon?.no_pegawai_pemohon || "",
namaPenghantar: permohonan.penghantar?.nama_penghantar || "",
pangkatPenghantar: permohonan.penghantar?.pangkat_penghantar || "",
noPegawaiPenghantar: permohonan.penghantar?.no_pegawai_penghantar || "",
ringkasanKenyataanKes: permohonan.ringkasan_kenyataan_kes || "",
bilangan: permohonan.bilangan || 0,
barangList: permohonan.report.map((barang) => ({
jenisBarangDetail: barang.jenis_barang || "",
tandaBarang: barang.tanda_barang || "",
keadaanBarang: barang.keadaan_barang || "",
kuantitiBarang: barang.kuantiti_barang || 0,
})),
noKertasSiasatan: permohonan.no_kertas_siasatan || "",
noLaporanPolis: permohonan.no_laporan_polis || "",
tarikhTemujanji:
permohonan.tarikh_temujanji?.toISOString().split("T")[0] || "",
slotMasa: permohonan.slot_masa
? new Date(permohonan.slot_masa).toLocaleTimeString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
: "",
isPenghantarSameAsPemohon: !permohonan.penghantar,
},
};
} catch (error) {
// Handle any unexpected errors
console.error("Error fetching permohonan:", error);
return {
statusCode: 500,
message: "Something went wrong while fetching permohonan data",
};
}
});

View File

@ -1,27 +0,0 @@
// File: server/api/permohonan/[noSiri].delete.js
export default defineEventHandler(async (event) => {
const { noSiri } = event.context.params;
if (!noSiri) {
return {
statusCode: 400,
message: "noSiri is required.",
};
}
try {
await prisma.permohonan.delete({
where: { no_siri: noSiri },
});
return {
statusCode: 200,
message: "Permohonan successfully deleted.",
};
} catch (error) {
return {
statusCode: 500,
message: "Failed to delete permohonan.",
};
}
});

View File

@ -0,0 +1,210 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const { userID } = event.context.user;
const { noSiri } = event.context.params; // Permohonan identifier from URL
const {
namaPemohon,
pangkatPemohon,
noPegawaiPemohon,
namaPenghantar,
pangkatPenghantar,
noPegawaiPenghantar,
ringkasanKenyataanKes,
isPenghantarSameAsPemohon,
bilangan,
barangList,
noKertasSiasatan,
noLaporanPolis,
tarikhTemujanji,
slotMasa,
isDraft,
} = body;
// 1. Mandatory fields validation
const mandatoryFields = [
namaPemohon,
pangkatPemohon,
noPegawaiPemohon,
ringkasanKenyataanKes,
bilangan,
noKertasSiasatan,
noLaporanPolis,
tarikhTemujanji,
slotMasa,
...barangList,
];
if (mandatoryFields.some((field) => !field || field === "")) {
return {
statusCode: 400,
message:
"Setiap medan mandatori yang bertanda * telah diisi. (Ralat CMN-E001)",
};
}
// 2. Validate date for the appointment
const appointmentDate = new Date(tarikhTemujanji);
const currentDate = new Date();
if (appointmentDate <= currentDate) {
return {
statusCode: 400,
message:
"Perlu memastikan tarikh janji temu yang dimasukkan adalah tarikh selepas tarikh semasa. (Ralat CMN-E002)",
};
}
// 3. Check if the session is expired
const sessionExpired = false; // Implement session logic here
if (sessionExpired) {
return {
statusCode: 401,
message:
"Sesi aktif pengguna berada dalam sistem telah tamat. (Ralat CMN-E005)",
};
}
// 4. Check for invalid symbols in text fields
const hasInvalidSymbols = (text) => /[^a-zA-Z0-9\s]/.test(text); // Allow alphanumeric and spaces
const fieldsToCheck = [
namaPemohon,
pangkatPemohon,
noPegawaiPemohon,
...barangList.map((barang) => barang.tandaBarang),
];
if (fieldsToCheck.some((field) => hasInvalidSymbols(field))) {
return {
statusCode: 400,
message:
"Perlu memastikan tiada penggunaan simbol dalam medan yang ditetapkan. (Ralat CMN-E011)",
};
}
// 5. Update the permohonan in the database
let permohonanStatus = isDraft ? "Permohonan Draf" : "Permohonan Dihantar";
try {
// Update the existing `permohonan` record
const updatedPermohonan = await prisma.permohonan.update({
where: {
no_siri: noSiri,
},
data: {
status_permohonan: permohonanStatus,
pemohon: {
update: {
userID: userID,
pangkat_pemohon: pangkatPemohon,
no_pegawai_pemohon: noPegawaiPemohon,
},
},
penghantar: isPenghantarSameAsPemohon
? null
: {
update: {
nama_penghantar: namaPenghantar,
pangkat_penghantar: pangkatPenghantar,
no_pegawai_penghantar: noPegawaiPenghantar,
},
},
ringkasan_kenyataan_kes: ringkasanKenyataanKes,
bilangan: parseInt(bilangan),
penghantar_sama_dengan_pemohon: isPenghantarSameAsPemohon ? 1 : 0,
no_kertas_siasatan: noKertasSiasatan,
no_laporan_polis: noLaporanPolis,
tarikh_temujanji: new Date(tarikhTemujanji),
slot_masa: new Date(`1970-01-01T${slotMasa}`),
modified_at: new Date(),
},
});
// Delete old barang and create new ones
await prisma.report.deleteMany({
where: { permohonanID: updatedPermohonan.id },
});
for (const barang of barangList) {
// await prisma.report.create({
// data: {
// permohonanID: updatedPermohonan.id,
// jenis_barang: barang.jenisBarangDetail,
// kuantiti_barang: barang.kuantitiBarang,
// tanda_barang: barang.tandaBarang,
// keadaan_barang: barang.keadaanBarang,
// create_by: userID,
// create_at: new Date(),
// },
// });
await prisma.report.create({
data: {
permohonanID: updatedPermohonan.id,
jenis_barang: barang.jenisBarangDetail,
create_by: userID,
create_at: new Date(),
},
});
}
// 6. Send confirmation email if not a draft
if (!isDraft) {
await sendEmail({
to: [
/* pemohon, pegawai_kaunter, ketua_bahagian */
],
subject: `Kemaskini Permohonan: ${noSiri}`,
body: `
Case ID: ${noSiri}
Appointment Date: ${tarikhTemujanji}
Time Slot: ${slotMasa}
Barang: ${barangList
.map(
(barang) =>
`${barang.tandaBarang} - ${barang.kuantitiBarang} units`
)
.join(", ")}
Pemohon Details: ${namaPemohon} (${pangkatPemohon})
${
isPenghantarSameAsPemohon
? ""
: `Penghantar Details: ${namaPenghantar} (${pangkatPenghantar})`
}
Ringkasan Kenyataan Kes: ${ringkasanKenyataanKes}
`,
});
}
return {
statusCode: 200,
message: isDraft
? "Rekod telah berjaya disimpan. (Status CMN-S001)"
: "Permohonan pemeriksaan forensik telah dikemaskini. (Status FOR-S001)",
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Terdapat masalah. Silakan cuba lagi.",
};
}
});
// Helper function to generate case reference number
const generateCaseReferenceID = () => {
const now = new Date();
const year = now.getFullYear().toString();
const month = String(now.getMonth() + 1).padStart(2, "0"); // JS month is zero-indexed
const day = String(now.getDate()).padStart(2, "0");
const uniqueSerial = String(Math.floor(Math.random() * 1000000)).padStart(
6,
"0"
);
return `${year}${month}${day}-${uniqueSerial}`;
};
const sendEmail = async ({ to, subject, body }) => {
console.log("Sending email to", to);
return true;
};

View File

@ -1,16 +1,40 @@
// File: server/api/permohonan/index.post.js
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const { userID } = event.context.user;
// Step 1: Validation for required fields
if (
!body.namaPemohon ||
!body.tarikhTemujanji ||
!body.noLaporanPolis ||
!body.slotMasa ||
!body.barangList.length
) {
const {
namaPemohon,
pangkatPemohon,
noPegawaiPemohon,
namaPenghantar,
pangkatPenghantar,
noPegawaiPenghantar,
ringkasanKenyataanKes,
isPenghantarSameAsPemohon,
bilangan,
barangList,
noKertasSiasatan,
noLaporanPolis,
tarikhTemujanji,
slotMasa,
isDraft,
} = body;
// 1. Mandatory fields validation
const mandatoryFields = [
namaPemohon,
pangkatPemohon,
noPegawaiPemohon,
ringkasanKenyataanKes,
bilangan,
noKertasSiasatan,
noLaporanPolis,
tarikhTemujanji,
slotMasa,
...barangList,
];
if (mandatoryFields.some((field) => !field || field === "")) {
return {
statusCode: 400,
message:
@ -18,9 +42,10 @@ export default defineEventHandler(async (event) => {
};
}
// Step 2: Validate appointment date must be in the future
// 2. Validate date for the appointment
const appointmentDate = new Date(tarikhTemujanji);
const currentDate = new Date();
const appointmentDate = new Date(body.tarikhTemujanji);
if (appointmentDate <= currentDate) {
return {
statusCode: 400,
@ -29,102 +54,150 @@ export default defineEventHandler(async (event) => {
};
}
// Step 3: Generate Case ID
const caseID = generateCaseID();
// 3. Check if the session is expired
const sessionExpired = false; // Implement session logic here
if (sessionExpired) {
return {
statusCode: 401,
message:
"Sesi aktif pengguna berada dalam sistem telah tamat. (Ralat CMN-E005)",
};
}
// Step 4: Determine status (Draft or Submitted)
const status = body.isDraft ? "Permohonan Draf" : "Permohonan Dihantar";
// 4. Check for invalid symbols in text fields
const hasInvalidSymbols = (text) => /[^a-zA-Z0-9\s]/.test(text); // Allow alphanumeric and spaces
const fieldsToCheck = [
namaPemohon,
pangkatPemohon,
noPegawaiPemohon,
...barangList.map((barang) => barang.tandaBarang),
];
if (fieldsToCheck.some((field) => hasInvalidSymbols(field))) {
return {
statusCode: 400,
message:
"Perlu memastikan tiada penggunaan simbol dalam medan yang ditetapkan. (Ralat CMN-E011)",
};
}
// 5. Insert data into the database
const caseReferenceID = generateCaseReferenceID();
let permohonanStatus = isDraft ? "Permohonan Draf" : "Permohonan Dihantar";
try {
// Step 5: Insert data into the permohonan table
// Insert into `permohonan` table
const newPermohonan = await prisma.permohonan.create({
data: {
no_siri: caseID,
pemohonID: body.pemohonID,
penghantarID: body.penghantarID || null, // If same as pemohon, this can be null
penghantar_sama_dengan_pemohon: body.penghantarSamaDenganPemohon
? 1
: 0,
status_permohonan: status, // Draf or Dihantar
ringkasan_kenyataan_kes: body.ringkasanKenyataanKes,
bilangan: body.bilangan?.toString(),
no_laporan_polis: body.noLaporanPolis,
tarikh_temujanji: new Date(body.tarikhTemujanji),
create_at: currentDate,
no_siri: caseReferenceID,
status_permohonan: permohonanStatus,
pemohon: {
create: {
userID: userID, // Assuming the user is authenticated, replace with actual user ID
pangkat_pemohon: pangkatPemohon,
no_pegawai_pemohon: noPegawaiPemohon,
},
},
penghantar: {
create: {
nama_penghantar: namaPenghantar,
pangkat_penghantar: pangkatPenghantar,
no_pegawai_penghantar: noPegawaiPenghantar,
},
},
ringkasan_kenyataan_kes: ringkasanKenyataanKes,
bilangan: parseInt(bilangan),
penghantar_sama_dengan_pemohon: isPenghantarSameAsPemohon ? 1 : 0,
no_kertas_siasatan: noKertasSiasatan,
no_laporan_polis: noLaporanPolis,
tarikh_temujanji: new Date(tarikhTemujanji),
slot_masa: new Date(`1970-01-01T${slotMasa}`), // Convert slotMasa string to Time
create_at: new Date(),
},
});
// Step 6: Insert `barang` details into the `permohonan_jenis_barang` table
const barangList = body.barangList.map((barang) => ({
permohonanID: newPermohonan.id, // Foreign key to permohonan
jenis_barang: barang.jenisBarang, // Foreign key to lookup(jenis_barang)
barang_status: "ACTIVE", // Static value for now
}));
await prisma.permohonan_jenis_barang.createMany({ data: barangList });
// Insert related `report` and `document` data
for (const barang of barangList) {
// await prisma.report.create({
// data: {
// permohonanID: newPermohonan.id,
// jenis_barang: barang.jenisBarangDetail,
// kuantiti_barang: barang.kuantitiBarang,
// tanda_barang: barang.tandaBarang,
// keadaan_barang: barang.keadaanBarang,
// create_by: userID,
// create_at: new Date(),
// },
// });
// Step 7: Insert data into the `report` table
const newReport = await prisma.report.create({
data: {
permohonanID: newPermohonan.id,
jenis_barang: body.barangList[0].jenisBarang, // The first barang's jenis_barang
peralatan: body.peralatan || null, // Optional, based on frontend input
langkah_langkah: body.langkahLangkah || null, // Optional, based on frontend input
gambarID: body.gambarID || null, // Optional document ID, if any
ulasan: body.ulasan || null, // Optional notes
dapatan: body.dapatan || null, // Optional foreign key lookup(dapatan)
create_at: currentDate,
create_by: body.userID, // The user who created the report
},
});
// Step 8: Send confirmation email (pseudocode)
sendEmail({
case_id: caseID,
selected_appointment_date: body.tarikhTemujanji,
time_slot: body.slotMasa,
barang_list: body.barangList
.map((barang) => barang.jenisBarang)
.join(", "),
});
// Step 9: Return response depending on the action (draft or submit)
if (body.isDraft) {
return {
statusCode: 200,
message: "Rekod telah berjaya disimpan. (Status CMN-S001)",
data: { newPermohonan, newReport },
};
} else {
return {
statusCode: 200,
message:
"Permohonan pemeriksaan forensik telah dihantar. (Status FOR-S001)",
data: { newPermohonan, newReport },
};
await prisma.report.create({
data: {
permohonanID: newPermohonan.id,
jenis_barang: barang.jenisBarangDetail,
create_by: userID,
create_at: new Date(),
},
});
}
// 6. Send confirmation email
if (!isDraft) {
await sendEmail({
to: [
/* pemohon, pegawai_kaunter, ketua_bahagian */
],
subject: `Permohonan Baru: ${caseReferenceID}`,
body: `
Case ID: ${caseReferenceID}
Appointment Date: ${tarikhTemujanji}
Time Slot: ${slotMasa}
Barang: ${barangList
.map(
(barang) =>
`${barang.tandaBarang} - ${barang.kuantitiBarang} units`
)
.join(", ")}
Pemohon Details: ${namaPemohon} (${pangkatPemohon})
${
isPenghantarSameAsPemohon
? ""
: `Penghantar Details: ${namaPenghantar} (${pangkatPenghantar})`
}
Ringkasan Kenyataan Kes: ${ringkasanKenyataanKes}
`,
});
}
return {
statusCode: 200,
message: isDraft
? "Rekod telah berjaya disimpan. (Status CMN-S001)"
: "Permohonan pemeriksaan forensik telah dihantar. (Status FOR-S001)",
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
message: "Failed to create permohonan and report. Please try again.",
message: "Terdapat masalah. Silakan cuba lagi.",
};
}
});
// Helper function to generate unique case ID
function generateCaseID() {
const date = new Date();
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const uniqueSerialNumber = Math.floor(Math.random() * 1000000)
.toString()
.padStart(6, "0");
return `${year}${month}${day}-${uniqueSerialNumber}`;
}
// Helper function to generate case reference number
const generateCaseReferenceID = () => {
const now = new Date();
const year = now.getFullYear().toString();
const month = String(now.getMonth() + 1).padStart(2, "0"); // JS month is zero-indexed
const day = String(now.getDate()).padStart(2, "0");
const uniqueSerial = String(Math.floor(Math.random() * 1000000)).padStart(
6,
"0"
);
return `${year}${month}${day}-${uniqueSerial}`;
};
// Pseudocode function for sending email
function sendEmail(data) {
// Email logic goes here
console.log("Email sent with the following data: ", data);
}
const sendEmail = async ({ to, subject, body }) => {
console.log("Sending email to", to);
return true;
};