feat(apbdes): make image and file optional for edit page too

Changes:

Backend (updt.ts, index.ts):
- Update FormUpdateBody: imageId?: string | null
- Update Elysia schema: t.Optional(t.String())
- Handle null/undefined values when updating

UI (edit/page.tsx):
- Remove mandatory validation for imageId and fileId
- Update labels to show '(Opsional)'
- Simplify handleSubmit logic (no validation check)
- Keep existing file IDs if no new upload

User Flow:
Before: Edit required imageId and fileId to be present
After: Can update APBDes without files, preserve existing or set to null

Files changed:
- src/app/api/[[...slugs]]/_lib/landing_page/apbdes/updt.ts
- src/app/api/[[...slugs]]/_lib/landing_page/apbdes/index.ts
- src/app/admin/(dashboard)/landing-page/apbdes/[id]/edit/page.tsx

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-05 15:53:26 +08:00
parent 4821934224
commit 159fb3cec6
7 changed files with 47 additions and 55 deletions

View File

@@ -23,8 +23,9 @@ const ApbdesFormSchema = z.object({
name: z.string().optional(), name: z.string().optional(),
deskripsi: z.string().optional(), deskripsi: z.string().optional(),
jumlah: z.string().optional(), jumlah: z.string().optional(),
imageId: z.string().min(1, "Gambar wajib diunggah"), // Image dan file opsional (bisa kosong)
fileId: z.string().min(1, "File wajib diunggah"), imageId: z.string().optional(),
fileId: z.string().optional(),
items: z.array(ApbdesItemSchema).min(1, "Minimal ada 1 item"), items: z.array(ApbdesItemSchema).min(1, "Minimal ada 1 item"),
}); });

View File

@@ -205,7 +205,6 @@ function EditAPBDes() {
// Upload file baru jika ada perubahan // Upload file baru jika ada perubahan
if (imageFile) { if (imageFile) {
// Hapus file lama dari form jika ada file baru
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({
file: imageFile, file: imageFile,
name: imageFile.name, name: imageFile.name,
@@ -217,7 +216,6 @@ function EditAPBDes() {
} }
if (docFile) { if (docFile) {
// Hapus file lama dari form jika ada file baru
const res = await ApiFetch.api.fileStorage.create.post({ const res = await ApiFetch.api.fileStorage.create.post({
file: docFile, file: docFile,
name: docFile.name, name: docFile.name,
@@ -228,15 +226,7 @@ function EditAPBDes() {
} }
} }
// Jika tidak ada file baru, gunakan ID lama (sudah ada di form) // Image dan file sekarang opsional, tidak perlu validasi
// Pastikan imageId dan fileId tetap ada
if (!apbdesState.edit.form.imageId) {
return toast.warn('Gambar wajib diunggah');
}
if (!apbdesState.edit.form.fileId) {
return toast.warn('Dokumen wajib diunggah');
}
const success = await apbdesState.edit.update(); const success = await apbdesState.edit.update();
if (success) { if (success) {
router.push('/admin/landing-page/apbdes'); router.push('/admin/landing-page/apbdes');
@@ -343,11 +333,11 @@ function EditAPBDes() {
required required
/> />
{/* Gambar & Dokumen */} {/* Gambar & Dokumen (Opsional) */}
<Stack gap="xs"> <Stack gap="xs">
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>
Gambar APBDes Gambar APBDes (Opsional)
</Text> </Text>
<Dropzone <Dropzone
onDrop={handleDrop('image')} onDrop={handleDrop('image')}
@@ -397,7 +387,7 @@ function EditAPBDes() {
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>
Dokumen APBDes Dokumen APBDes (Opsional)
</Text> </Text>
<Dropzone <Dropzone
onDrop={handleDrop('doc')} onDrop={handleDrop('doc')}

View File

@@ -46,13 +46,9 @@ function CreateAPBDes() {
const [docFile, setDocFile] = useState<File | null>(null); const [docFile, setDocFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
// Check if form is valid // Check if form is valid - hanya cek items, gambar dan file opsional
const isFormValid = () => { const isFormValid = () => {
return ( return stateAPBDes.create.form.items.length > 0;
imageFile !== null &&
docFile !== null &&
stateAPBDes.create.form.items.length > 0
);
}; };
// Form sementara untuk input item baru // Form sementara untuk input item baru
@@ -84,28 +80,34 @@ function CreateAPBDes() {
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
if (!imageFile || !docFile) {
return toast.warn("Pilih gambar dan dokumen terlebih dahulu");
}
if (stateAPBDes.create.form.items.length === 0) { if (stateAPBDes.create.form.items.length === 0) {
return toast.warn("Minimal tambahkan 1 item APBDes"); return toast.warn("Minimal tambahkan 1 item APBDes");
} }
try { try {
setIsSubmitting(true); setIsSubmitting(true);
const [uploadImageRes, uploadDocRes] = await Promise.all([
ApiFetch.api.fileStorage.create.post({ file: imageFile, name: imageFile.name }),
ApiFetch.api.fileStorage.create.post({ file: docFile, name: docFile.name }),
]);
const imageId = uploadImageRes?.data?.data?.id; // Upload files hanya jika ada file yang dipilih
const fileId = uploadDocRes?.data?.data?.id; let imageId = '';
let fileId = '';
if (!imageId || !fileId) { if (imageFile) {
return toast.error("Gagal mengupload file"); const uploadImageRes = await ApiFetch.api.fileStorage.create.post({
file: imageFile,
name: imageFile.name,
});
imageId = uploadImageRes?.data?.data?.id || '';
} }
// Update form dengan ID file if (docFile) {
const uploadDocRes = await ApiFetch.api.fileStorage.create.post({
file: docFile,
name: docFile.name,
});
fileId = uploadDocRes?.data?.data?.id || '';
}
// Update form dengan ID file (bisa kosong)
stateAPBDes.create.form.imageId = imageId; stateAPBDes.create.form.imageId = imageId;
stateAPBDes.create.form.fileId = fileId; stateAPBDes.create.form.fileId = fileId;
@@ -174,12 +176,16 @@ function CreateAPBDes() {
style={{ border: '1px solid #e0e0e0' }} style={{ border: '1px solid #e0e0e0' }}
> >
<Stack gap="md"> <Stack gap="md">
{/* Gambar & Dokumen (dipendekkan untuk fokus pada items) */} {/* Info: File opsional */}
<Text fz="sm" c="dimmed" mb="xs">
* Upload gambar dan dokumen bersifat opsional. Bisa dikosongkan jika belum ada.
</Text>
<Stack gap={"xs"}> <Stack gap={"xs"}>
{/* Gambar APBDes */} {/* Gambar APBDes */}
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>
Gambar APBDes Gambar APBDes (Opsional)
</Text> </Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={(files) => {
@@ -249,10 +255,10 @@ function CreateAPBDes() {
)} )}
</Box> </Box>
{/* Dokumen APBDes */} {/* Dokumen APBDes (Opsional) */}
<Box> <Box>
<Text fw="bold" fz="sm" mb={6}> <Text fw="bold" fz="sm" mb={6}>
Dokumen APBDes Dokumen APBDes (Opsional)
</Text> </Text>
<Dropzone <Dropzone
onDrop={(files) => { onDrop={(files) => {

View File

@@ -17,8 +17,8 @@ type FormCreate = {
name?: string; name?: string;
deskripsi?: string; deskripsi?: string;
jumlah?: string; jumlah?: string;
imageId: string; imageId?: string | null; // Opsional
fileId: string; fileId?: string | null; // Opsional
items: APBDesItemInput[]; items: APBDesItemInput[];
}; };
@@ -32,12 +32,7 @@ export default async function apbdesCreate(context: Context) {
if (!body.tahun) { if (!body.tahun) {
throw new Error('Tahun is required'); throw new Error('Tahun is required');
} }
if (!body.imageId) { // Image dan file sekarang opsional
throw new Error('Image ID is required');
}
if (!body.fileId) {
throw new Error('File ID is required');
}
if (!body.items || body.items.length === 0) { if (!body.items || body.items.length === 0) {
throw new Error('At least one item is required'); throw new Error('At least one item is required');
} }
@@ -50,8 +45,8 @@ export default async function apbdesCreate(context: Context) {
name: body.name || `APBDes Tahun ${body.tahun}`, name: body.name || `APBDes Tahun ${body.tahun}`,
deskripsi: body.deskripsi, deskripsi: body.deskripsi,
jumlah: body.jumlah, jumlah: body.jumlah,
imageId: body.imageId, imageId: body.imageId || null, // null jika tidak ada
fileId: body.fileId, fileId: body.fileId || null, // null jika tidak ada
}, },
}); });

View File

@@ -36,8 +36,8 @@ const APBDes = new Elysia({
name: t.Optional(t.String()), name: t.Optional(t.String()),
deskripsi: t.Optional(t.String()), deskripsi: t.Optional(t.String()),
jumlah: t.Optional(t.String()), jumlah: t.Optional(t.String()),
imageId: t.String(), imageId: t.Optional(t.String()),
fileId: t.String(), fileId: t.Optional(t.String()),
items: t.Array(ApbdesItemSchema), items: t.Array(ApbdesItemSchema),
}), }),
}) })
@@ -50,8 +50,8 @@ const APBDes = new Elysia({
name: t.Optional(t.String()), name: t.Optional(t.String()),
deskripsi: t.Optional(t.String()), deskripsi: t.Optional(t.String()),
jumlah: t.Optional(t.String()), jumlah: t.Optional(t.String()),
imageId: t.String(), imageId: t.Optional(t.String()),
fileId: t.String(), fileId: t.Optional(t.String()),
items: t.Array(ApbdesItemSchema), items: t.Array(ApbdesItemSchema),
}), }),
}) })

View File

@@ -15,8 +15,8 @@ type FormUpdateBody = {
name?: string; name?: string;
deskripsi?: string; deskripsi?: string;
jumlah?: string; jumlah?: string;
imageId: string; imageId?: string | null;
fileId: string; fileId?: string | null;
items: APBDesItemInput[]; items: APBDesItemInput[];
}; };

View File

@@ -134,7 +134,7 @@ export default function FixedPlayerBar() {
p="sm" p="sm"
shadow="lg" shadow="lg"
style={{ style={{
zIndex: 1000, zIndex: 1,
borderTop: '1px solid rgba(0,0,0,0.1)', borderTop: '1px solid rgba(0,0,0,0.1)',
}} }}
> >