Compare commits

...

8 Commits

Author SHA1 Message Date
09c7fd8f3a fix: fileStorage path issues between local and staging environments
- Store relative paths in database instead of absolute paths
- Reconstruct absolute paths at runtime using UPLOAD_DIR env var
- Add VOLUME for /app/uploads in Dockerfile for persistent storage
- Create uploads directory with proper permissions in Docker
- Add error handling for missing UPLOAD_DIR in findUniq.ts
- Simplify GitHub workflow memory in QWEN.md (manual handling)

This fixes the 500 errors on staging for file create/delete operations
caused by environment-specific absolute paths stored in database.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 10:57:51 +08:00
656ffcc561 bump: version 0.1.7 -> 0.1.8 - add kependudukan migration
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:41:16 +08:00
76ffa662c5 fix(database): add migration for kependudukan tables
- Add DataBanjar, DistribusiAgama, DistribusiUmur, MigrasiPenduduk, DinamikaPenduduk tables
- Add indexes for performance (tahun, isActive)
- Add JenisMigrasi enum (MASUK, KELUAR)
- Fixes: Error 500 on all CRUD kependudukan endpoints in staging

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:40:53 +08:00
46423409fd bump: version 0.1.6 -> 0.1.7 - auto migration on startup
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:01:08 +08:00
2edf5e9b11 fix(deployment): add auto database migration on container startup
- Create docker-entrypoint.sh to run prisma migrate deploy before app start
- Update Dockerfile to use entrypoint script
- Ensures database schema is always up-to-date after deployment
- Fixes: CRUD kependudukan error 500 di staging karena tabel belum dibuat

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:00:53 +08:00
af368eeee0 bump: version 0.1.5 -> 0.1.6 - fix migrasi penduduk schema mismatch
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 16:08:10 +08:00
e104cd8fcc docs: update QWEN.md
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 16:08:01 +08:00
50801e5c8a fix(kependudukan): remove jenisKelamin field and align MigrasiPenduduk with database schema
- Remove jenisKelamin field from API, state, and UI components
- Fix MigrasiPenduduk API to use null instead of undefined for optional fields
- Update create/edit forms to properly handle asal/tujuan fields based on jenis
- Fix DatePickerInput type handling with valueFormat prop
- Update list page to display asal or tujuan conditionally
- Add proper select statements in API responses
- Fix TypeScript type errors in migrasi-penduduk module

Closes: Schema mismatch causing errors when inputting migrasi penduduk data

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 15:53:58 +08:00
15 changed files with 197 additions and 81 deletions

View File

@@ -59,9 +59,16 @@ COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nextjs:nodejs /app/next.config.* ./ COPY --from=builder --chown=nextjs:nodejs /app/next.config.* ./
COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
# Create uploads directory with proper permissions
RUN mkdir -p /app/uploads && chown nextjs:nodejs /app/uploads
USER nextjs USER nextjs
# Persistent storage for uploaded files
VOLUME ["/app/uploads"]
EXPOSE 3000 EXPOSE 3000
CMD ["bun", "start"] CMD ["/app/docker-entrypoint.sh"]

View File

@@ -229,4 +229,7 @@ Common issues and solutions:
3. Test database changes with `bunx prisma db push` 3. Test database changes with `bunx prisma db push`
4. Use the integrated Swagger docs at `/api/docs` for API testing 4. Use the integrated Swagger docs at `/api/docs` for API testing
5. Check environment variables are properly configured 5. Check environment variables are properly configured
6. Verify responsive design on different screen sizes 6. Verify responsive design on different screen sizes
## Qwen Added Memories
- **GitHub Workflows**: Project ini memiliki workflow GitHub Action untuk deployment. User akan menangani workflow secara manual di GitHub.

13
docker-entrypoint.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
set -e
echo "🔄 Running database migrations..."
cd /app
bunx prisma migrate deploy || {
echo "❌ Migration failed!"
exit 1
}
echo "✅ Migrations completed successfully"
echo "🚀 Starting application..."
exec bun start

View File

@@ -1,6 +1,6 @@
{ {
"name": "desa-darmasaba", "name": "desa-darmasaba",
"version": "0.1.5", "version": "0.1.8",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",

View File

@@ -0,0 +1,112 @@
-- CreateTable
CREATE TABLE "DataBanjar" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"penduduk" INTEGER NOT NULL,
"kk" INTEGER NOT NULL,
"miskin" INTEGER NOT NULL,
"tahun" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DataBanjar_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DistribusiAgama" (
"id" TEXT NOT NULL,
"agama" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"tahun" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DistribusiAgama_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DistribusiUmur" (
"id" TEXT NOT NULL,
"rentangUmur" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"tahun" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DistribusiUmur_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MigrasiPenduduk" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jenis" "JenisMigrasi" NOT NULL,
"tanggal" TIMESTAMP(3) NOT NULL,
"asal" TEXT,
"tujuan" TEXT,
"alasan" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "MigrasiPenduduk_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DinamikaPenduduk" (
"id" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"kelahiran" INTEGER NOT NULL,
"kematian" INTEGER NOT NULL,
"masuk" INTEGER NOT NULL,
"keluar" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DinamikaPenduduk_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "DataBanjar_tahun_idx" ON "DataBanjar"("tahun");
-- CreateIndex
CREATE INDEX "DataBanjar_isActive_idx" ON "DataBanjar"("isActive");
-- CreateIndex
CREATE INDEX "DistribusiAgama_tahun_idx" ON "DistribusiAgama"("tahun");
-- CreateIndex
CREATE INDEX "DistribusiAgama_isActive_idx" ON "DistribusiAgama"("isActive");
-- CreateIndex
CREATE INDEX "DistribusiUmur_tahun_idx" ON "DistribusiUmur"("tahun");
-- CreateIndex
CREATE INDEX "DistribusiUmur_isActive_idx" ON "DistribusiUmur"("isActive");
-- CreateIndex
CREATE INDEX "MigrasiPenduduk_tanggal_idx" ON "MigrasiPenduduk"("tanggal");
-- CreateIndex
CREATE INDEX "MigrasiPenduduk_isActive_idx" ON "MigrasiPenduduk"("isActive");
-- CreateIndex
CREATE INDEX "DinamikaPenduduk_tahun_idx" ON "DinamikaPenduduk"("tahun");
-- CreateIndex
CREATE INDEX "DinamikaPenduduk_isActive_idx" ON "DinamikaPenduduk"("isActive");
-- CreateIndex
CREATE UNIQUE INDEX "DinamikaPenduduk_tahun_key" ON "DinamikaPenduduk"("tahun");
-- CreateEnum
CREATE TYPE "JenisMigrasi" AS ENUM ('MASUK', 'KELUAR');

View File

@@ -9,7 +9,6 @@ const templateMigrasiPenduduk = z.object({
tanggal: z.string().min(1, "Tanggal harus diisi"), tanggal: z.string().min(1, "Tanggal harus diisi"),
asalTujuan: z.string().min(1, "Asal/Tujuan harus diisi"), asalTujuan: z.string().min(1, "Asal/Tujuan harus diisi"),
alasan: z.string().optional(), alasan: z.string().optional(),
jenisKelamin: z.string().optional(),
}); });
const migrasiPenduduk = proxy({ const migrasiPenduduk = proxy({
@@ -20,7 +19,6 @@ const migrasiPenduduk = proxy({
tanggal: "", tanggal: "",
asalTujuan: "", asalTujuan: "",
alasan: "", alasan: "",
jenisKelamin: "",
}, },
loading: false, loading: false,
async create() { async create() {
@@ -38,7 +36,7 @@ const migrasiPenduduk = proxy({
const id = res.data?.data?.id; const id = res.data?.data?.id;
if (id) { if (id) {
toast.success("Sukses menambahkan data migrasi penduduk"); toast.success("Sukses menambahkan data migrasi penduduk");
migrasiPenduduk.create.form = { jenis: "", nama: "", tanggal: "", asalTujuan: "", alasan: "", jenisKelamin: "" }; migrasiPenduduk.create.form = { jenis: "", nama: "", tanggal: "", asalTujuan: "", alasan: "" };
migrasiPenduduk.findMany.load(); migrasiPenduduk.findMany.load();
return id; return id;
} }
@@ -116,7 +114,6 @@ const migrasiPenduduk = proxy({
tanggal: "", tanggal: "",
asalTujuan: "", asalTujuan: "",
alasan: "", alasan: "",
jenisKelamin: "",
}, },
loading: false, loading: false,
async submit() { async submit() {
@@ -132,7 +129,6 @@ const migrasiPenduduk = proxy({
tanggal: this.form.tanggal, tanggal: this.form.tanggal,
asalTujuan: this.form.asalTujuan, asalTujuan: this.form.asalTujuan,
alasan: this.form.alasan, alasan: this.form.alasan,
jenisKelamin: this.form.jenisKelamin,
}; };
const cek = templateMigrasiPenduduk.safeParse(formData); const cek = templateMigrasiPenduduk.safeParse(formData);

View File

@@ -28,7 +28,6 @@ interface MigrasiPendudukForm {
tanggal: string; tanggal: string;
asalTujuan: string; asalTujuan: string;
alasan: string; alasan: string;
jenisKelamin: string;
} }
export default function EditMigrasiPenduduk() { export default function EditMigrasiPenduduk() {
@@ -42,7 +41,6 @@ export default function EditMigrasiPenduduk() {
tanggal: '', tanggal: '',
asalTujuan: '', asalTujuan: '',
alasan: '', alasan: '',
jenisKelamin: '',
}); });
const [originalData, setOriginalData] = useState<MigrasiPendudukForm>({ const [originalData, setOriginalData] = useState<MigrasiPendudukForm>({
jenis: '', jenis: '',
@@ -50,7 +48,6 @@ export default function EditMigrasiPenduduk() {
tanggal: '', tanggal: '',
asalTujuan: '', asalTujuan: '',
alasan: '', alasan: '',
jenisKelamin: '',
}); });
const jenisOptions = [ const jenisOptions = [
@@ -58,11 +55,6 @@ export default function EditMigrasiPenduduk() {
{ value: 'KELUAR', label: 'Keluar' }, { value: 'KELUAR', label: 'Keluar' },
]; ];
const jenisKelaminOptions = [
{ value: 'L', label: 'Laki-laki' },
{ value: 'P', label: 'Perempuan' },
];
const isFormValid = () => { const isFormValid = () => {
return ( return (
formData.jenis?.trim() !== '' && formData.jenis?.trim() !== '' &&
@@ -81,23 +73,23 @@ export default function EditMigrasiPenduduk() {
stateMigrasiPenduduk.update.id = id; stateMigrasiPenduduk.update.id = id;
await stateMigrasiPenduduk.findUnique.load(id); await stateMigrasiPenduduk.findUnique.load(id);
const data = stateMigrasiPenduduk.findUnique.data as MigrasiPendudukForm | null; const data = stateMigrasiPenduduk.findUnique.data as any;
if (data) { if (data) {
const asalTujuan = data.jenis === 'MASUK' ? (data.asal || '') : (data.tujuan || '');
setFormData({ setFormData({
jenis: data.jenis ?? '', jenis: data.jenis ?? '',
nama: data.nama ?? '', nama: data.nama ?? '',
tanggal: data.tanggal ?? '', tanggal: data.tanggal ? new Date(data.tanggal).toISOString().split('T')[0] : '',
asalTujuan: data.asalTujuan ?? '', asalTujuan: asalTujuan,
alasan: data.alasan ?? '', alasan: data.alasan ?? '',
jenisKelamin: data.jenisKelamin ?? '',
}); });
setOriginalData({ setOriginalData({
jenis: data.jenis ?? '', jenis: data.jenis ?? '',
nama: data.nama ?? '', nama: data.nama ?? '',
tanggal: data.tanggal ?? '', tanggal: data.tanggal ? new Date(data.tanggal).toISOString().split('T')[0] : '',
asalTujuan: data.asalTujuan ?? '', asalTujuan: asalTujuan,
alasan: data.alasan ?? '', alasan: data.alasan ?? '',
jenisKelamin: data.jenisKelamin ?? '',
}); });
} }
} catch (error) { } catch (error) {
@@ -114,7 +106,7 @@ export default function EditMigrasiPenduduk() {
const handleChangeText = useCallback( const handleChangeText = useCallback(
(field: keyof MigrasiPendudukForm) => (field: keyof MigrasiPendudukForm) =>
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData((prev) => ({ ...prev, [field]: e.currentTarget.value as never })); setFormData((prev) => ({ ...prev, [field]: e.currentTarget.value }));
}, },
[] []
); );
@@ -122,7 +114,7 @@ export default function EditMigrasiPenduduk() {
const handleChangeSelect = useCallback( const handleChangeSelect = useCallback(
(field: keyof MigrasiPendudukForm) => (field: keyof MigrasiPendudukForm) =>
(value: string | null) => { (value: string | null) => {
setFormData((prev) => ({ ...prev, [field]: (value || '') as never })); setFormData((prev) => ({ ...prev, [field]: value || '' }));
}, },
[] []
); );
@@ -134,7 +126,6 @@ export default function EditMigrasiPenduduk() {
tanggal: originalData.tanggal, tanggal: originalData.tanggal,
asalTujuan: originalData.asalTujuan, asalTujuan: originalData.asalTujuan,
alasan: originalData.alasan, alasan: originalData.alasan,
jenisKelamin: originalData.jenisKelamin,
}); });
toast.info("Form dikembalikan ke data awal"); toast.info("Form dikembalikan ke data awal");
}; };
@@ -211,6 +202,7 @@ export default function EditMigrasiPenduduk() {
tanggal: val || '', tanggal: val || '',
})); }));
}} }}
valueFormat="YYYY-MM-DD"
required required
/> />
@@ -231,14 +223,6 @@ export default function EditMigrasiPenduduk() {
minRows={2} minRows={2}
/> />
<Select
label="Jenis Kelamin"
placeholder="Pilih jenis kelamin"
data={jenisKelaminOptions}
value={formData.jenisKelamin}
onChange={handleChangeSelect('jenisKelamin')}
/>
<Group justify="flex-end"> <Group justify="flex-end">
<Button <Button
variant="outline" variant="outline"

View File

@@ -31,11 +31,6 @@ function CreateMigrasiPenduduk() {
{ value: 'KELUAR', label: 'Keluar' }, { value: 'KELUAR', label: 'Keluar' },
]; ];
const jenisKelaminOptions = [
{ value: 'L', label: 'Laki-laki' },
{ value: 'P', label: 'Perempuan' },
];
const isFormValid = () => { const isFormValid = () => {
return ( return (
stateMigrasiPenduduk.create.form.jenis?.trim() !== '' && stateMigrasiPenduduk.create.form.jenis?.trim() !== '' &&
@@ -52,12 +47,12 @@ function CreateMigrasiPenduduk() {
tanggal: '', tanggal: '',
asalTujuan: '', asalTujuan: '',
alasan: '', alasan: '',
jenisKelamin: '',
}; };
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
setIsSubmitting(true);
const id = await stateMigrasiPenduduk.create.create(); const id = await stateMigrasiPenduduk.create.create();
if (id) { if (id) {
resetForm(); resetForm();
@@ -126,6 +121,7 @@ function CreateMigrasiPenduduk() {
onChange={(val: string | null) => { onChange={(val: string | null) => {
stateMigrasiPenduduk.create.form.tanggal = val || ''; stateMigrasiPenduduk.create.form.tanggal = val || '';
}} }}
valueFormat="YYYY-MM-DD"
required required
/> />
@@ -150,16 +146,6 @@ function CreateMigrasiPenduduk() {
minRows={2} minRows={2}
/> />
<Select
label="Jenis Kelamin"
placeholder="Pilih jenis kelamin"
data={jenisKelaminOptions}
value={stateMigrasiPenduduk.create.form.jenisKelamin}
onChange={(val) => {
stateMigrasiPenduduk.create.form.jenisKelamin = val || '';
}}
/>
<Group justify="right"> <Group justify="right">
<Button <Button
variant="outline" variant="outline"

View File

@@ -57,9 +57,9 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
jenis: string; jenis: string;
nama: string; nama: string;
tanggal: string; tanggal: string;
asalTujuan: string; asal: string | null;
tujuan: string | null;
alasan: string | null; alasan: string | null;
jenisKelamin: string | null;
}; };
const router = useRouter(); const router = useRouter();
@@ -142,16 +142,17 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
<TableTr> <TableTr>
<TableTh style={{ width: '10%' }}>Jenis</TableTh> <TableTh style={{ width: '10%' }}>Jenis</TableTh>
<TableTh style={{ width: '20%' }}>Nama</TableTh> <TableTh style={{ width: '20%' }}>Nama</TableTh>
<TableTh style={{ width: '12%' }}>Tanggal</TableTh> <TableTh style={{ width: '15%' }}>Tanggal</TableTh>
<TableTh style={{ width: '20%' }}>Asal/Tujuan</TableTh> <TableTh style={{ width: '25%' }}>Asal/Tujuan</TableTh>
<TableTh style={{ width: '10%' }}>L/P</TableTh>
<TableTh style={{ width: '10%' }}>Edit</TableTh> <TableTh style={{ width: '10%' }}>Edit</TableTh>
<TableTh style={{ width: '10%' }}>Hapus</TableTh> <TableTh style={{ width: '10%' }}>Hapus</TableTh>
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => {
const asalTujuan = item.jenis === 'MASUK' ? (item.asal || '-') : (item.tujuan || '-');
return (
<TableTr key={item.id}> <TableTr key={item.id}>
<TableTd> <TableTd>
<Text <Text
@@ -164,8 +165,7 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
</TableTd> </TableTd>
<TableTd>{item.nama}</TableTd> <TableTd>{item.nama}</TableTd>
<TableTd>{formatTanggal(item.tanggal)}</TableTd> <TableTd>{formatTanggal(item.tanggal)}</TableTd>
<TableTd>{item.asalTujuan}</TableTd> <TableTd>{asalTujuan}</TableTd>
<TableTd>{item.jenisKelamin || '-'}</TableTd>
<TableTd> <TableTd>
<Button <Button
variant="light" variant="light"
@@ -197,10 +197,11 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
</Button> </Button>
</TableTd> </TableTd>
</TableTr> </TableTr>
)) );
})
) : ( ) : (
<TableTr> <TableTr>
<TableTd colSpan={7}> <TableTd colSpan={6}>
<Center py={20}> <Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}> <Text c="dimmed" fz="sm" lh={1.4}>
Tidak ada data migrasi penduduk yang cocok Tidak ada data migrasi penduduk yang cocok
@@ -217,7 +218,9 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
<Box hiddenFrom="md"> <Box hiddenFrom="md">
<Stack gap="xs"> <Stack gap="xs">
{filteredData.length > 0 ? ( {filteredData.length > 0 ? (
filteredData.map((item) => ( filteredData.map((item) => {
const asalTujuan = item.jenis === 'MASUK' ? (item.asal || '-') : (item.tujuan || '-');
return (
<Paper key={item.id} withBorder p="sm" radius="sm"> <Paper key={item.id} withBorder p="sm" radius="sm">
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Box> <Box>
@@ -250,10 +253,10 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
</Box> </Box>
<Box> <Box>
<Text fz="sm" fw={600} lh={1.4}> <Text fz="sm" fw={600} lh={1.4}>
Asal/Tujuan {item.jenis === 'MASUK' ? 'Asal' : 'Tujuan'}
</Text> </Text>
<Text fz="sm" fw={500} lh={1.4}> <Text fz="sm" fw={500} lh={1.4}>
{item.asalTujuan} {asalTujuan}
</Text> </Text>
</Box> </Box>
{item.alasan && ( {item.alasan && (
@@ -266,14 +269,6 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
</Text> </Text>
</Box> </Box>
)} )}
<Box>
<Text fz="sm" fw={600} lh={1.4}>
Jenis Kelamin
</Text>
<Text fz="sm" fw={500} lh={1.4}>
{item.jenisKelamin || '-'}
</Text>
</Box>
<Group justify="flex-end" gap="xs"> <Group justify="flex-end" gap="xs">
<Button <Button
variant="light" variant="light"
@@ -304,7 +299,8 @@ function ListMigrasiPenduduk({ search, year }: { search: string; year?: number }
</Group> </Group>
</Stack> </Stack>
</Paper> </Paper>
)) );
})
) : ( ) : (
<Center py={20}> <Center py={20}>
<Text c="dimmed" fz="sm" lh={1.4}> <Text c="dimmed" fz="sm" lh={1.4}>

View File

@@ -79,7 +79,7 @@ const fileStorageCreate = async (context: Context) => {
data: { data: {
name: finalName, name: finalName,
realName: file.name, realName: file.name,
path: rootPath, path: pathName, // Store relative path (e.g., "images", "audio", "documents")
mimeType: finalMimeType, mimeType: finalMimeType,
category, category,
link: `/api/fileStorage/findUnique/${finalName}`, link: `/api/fileStorage/findUnique/${finalName}`,

View File

@@ -36,7 +36,7 @@ const fileStorageDelete = async (context: Context) => {
}; };
} }
const filePath = path.join(file.path, file.name); const filePath = path.join(UPLOAD_DIR, file.path, file.name);
try { try {
// Hapus file dari filesystem // Hapus file dari filesystem

View File

@@ -3,6 +3,8 @@ import { Context } from "elysia";
import fs from "fs/promises"; import fs from "fs/promises";
import path from "path"; import path from "path";
const UPLOAD_DIR = process.env.WIBU_UPLOAD_DIR;
const fileStorageFindUnique = async (context: Context) => { const fileStorageFindUnique = async (context: Context) => {
const { name } = context.params; const { name } = context.params;
@@ -20,9 +22,17 @@ const fileStorageFindUnique = async (context: Context) => {
}; };
} }
if (!UPLOAD_DIR) {
context.set.status = "Internal Server Error";
return {
status: 500,
message: "UPLOAD_DIR is not defined",
};
}
console.log(data); console.log(data);
const file = await fs.readFile(path.join(data.path, data.name)); const file = await fs.readFile(path.join(UPLOAD_DIR, data.path, data.name));
context.set.headers = { context.set.headers = {
"Content-Type": data.mimeType, "Content-Type": data.mimeType,
"Content-Length": file.length, "Content-Length": file.length,

View File

@@ -19,8 +19,8 @@ export default async function migrasiPendudukCreate(context: Context) {
jenis: body.jenis as 'MASUK' | 'KELUAR', jenis: body.jenis as 'MASUK' | 'KELUAR',
nama: body.nama, nama: body.nama,
tanggal: new Date(body.tanggal), tanggal: new Date(body.tanggal),
asal: isMasuk ? body.asalTujuan : undefined, asal: isMasuk ? body.asalTujuan : null,
tujuan: !isMasuk ? body.asalTujuan : undefined, tujuan: !isMasuk ? body.asalTujuan : null,
alasan: body.alasan, alasan: body.alasan,
}, },
select: { select: {
@@ -28,6 +28,8 @@ export default async function migrasiPendudukCreate(context: Context) {
jenis: true, jenis: true,
nama: true, nama: true,
tanggal: true, tanggal: true,
asal: true,
tujuan: true,
alasan: true, alasan: true,
} }
}); });

View File

@@ -21,7 +21,6 @@ const MigrasiPenduduk = new Elysia({
tanggal: t.String(), tanggal: t.String(),
asalTujuan: t.String(), asalTujuan: t.String(),
alasan: t.Optional(t.String()), alasan: t.Optional(t.String()),
jenisKelamin: t.Optional(t.String()),
}), }),
}) })
.put("/:id", migrasiPendudukUpdate, { .put("/:id", migrasiPendudukUpdate, {
@@ -34,7 +33,6 @@ const MigrasiPenduduk = new Elysia({
tanggal: t.String(), tanggal: t.String(),
asalTujuan: t.String(), asalTujuan: t.String(),
alasan: t.Optional(t.String()), alasan: t.Optional(t.String()),
jenisKelamin: t.Optional(t.String()),
}), }),
}) })
.delete("/del/:id", migrasiPendudukDelete, { .delete("/del/:id", migrasiPendudukDelete, {

View File

@@ -38,10 +38,19 @@ export default async function migrasiPendudukUpdate(context: Context) {
jenis: jenis as 'MASUK' | 'KELUAR', jenis: jenis as 'MASUK' | 'KELUAR',
nama, nama,
tanggal: new Date(tanggal), tanggal: new Date(tanggal),
asal: jenis === 'MASUK' ? asalTujuan : undefined, asal: jenis === 'MASUK' ? asalTujuan : null,
tujuan: jenis === 'KELUAR' ? asalTujuan : undefined, tujuan: jenis === 'KELUAR' ? asalTujuan : null,
alasan, alasan,
}, },
select: {
id: true,
jenis: true,
nama: true,
tanggal: true,
asal: true,
tujuan: true,
alasan: true,
},
}) })
return { return {