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(),
deskripsi: z.string().optional(),
jumlah: z.string().optional(),
imageId: z.string().min(1, "Gambar wajib diunggah"),
fileId: z.string().min(1, "File wajib diunggah"),
// Image dan file opsional (bisa kosong)
imageId: z.string().optional(),
fileId: z.string().optional(),
items: z.array(ApbdesItemSchema).min(1, "Minimal ada 1 item"),
});

View File

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

View File

@@ -46,13 +46,9 @@ function CreateAPBDes() {
const [docFile, setDocFile] = useState<File | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
// Check if form is valid
// Check if form is valid - hanya cek items, gambar dan file opsional
const isFormValid = () => {
return (
imageFile !== null &&
docFile !== null &&
stateAPBDes.create.form.items.length > 0
);
return stateAPBDes.create.form.items.length > 0;
};
// Form sementara untuk input item baru
@@ -84,28 +80,34 @@ function CreateAPBDes() {
};
const handleSubmit = async () => {
if (!imageFile || !docFile) {
return toast.warn("Pilih gambar dan dokumen terlebih dahulu");
}
if (stateAPBDes.create.form.items.length === 0) {
return toast.warn("Minimal tambahkan 1 item APBDes");
}
try {
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;
const fileId = uploadDocRes?.data?.data?.id;
// Upload files hanya jika ada file yang dipilih
let imageId = '';
let fileId = '';
if (!imageId || !fileId) {
return toast.error("Gagal mengupload file");
if (imageFile) {
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.fileId = fileId;
@@ -174,12 +176,16 @@ function CreateAPBDes() {
style={{ border: '1px solid #e0e0e0' }}
>
<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"}>
{/* Gambar APBDes */}
<Box>
<Text fw="bold" fz="sm" mb={6}>
Gambar APBDes
Gambar APBDes (Opsional)
</Text>
<Dropzone
onDrop={(files) => {
@@ -249,10 +255,10 @@ function CreateAPBDes() {
)}
</Box>
{/* Dokumen APBDes */}
{/* Dokumen APBDes (Opsional) */}
<Box>
<Text fw="bold" fz="sm" mb={6}>
Dokumen APBDes
Dokumen APBDes (Opsional)
</Text>
<Dropzone
onDrop={(files) => {

View File

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

View File

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

View File

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

View File

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