[darmasaba-dashboard][2026-03-27] feat: modular seeders and database-backed dashboard

- Split seeders into modular files per feature category
- Added seed:auth, seed:demographics, seed:divisions, seed:services, seed:dashboard commands
- Connected dashboard components to live database (Budget, SDGs, Satisfaction)
- Added API endpoints: /api/dashboard/budget, /api/dashboard/sdgs, /api/dashboard/satisfaction
- Updated prisma schema with dashboard metrics models
- Added loading states to dashboard components
- Fixed header navigation to /admin

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-27 12:14:19 +08:00
parent 34804127c5
commit 44b6b158ef
17 changed files with 1616 additions and 416 deletions

View File

@@ -0,0 +1,58 @@
/*
Warnings:
- You are about to drop the `Budget` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "Budget" DROP CONSTRAINT "Budget_approvedBy_fkey";
-- DropTable
DROP TABLE "Budget";
-- CreateTable
CREATE TABLE "budget" (
"id" TEXT NOT NULL,
"category" TEXT NOT NULL,
"amount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"percentage" DOUBLE PRECISION NOT NULL DEFAULT 0,
"color" TEXT NOT NULL DEFAULT '#3B82F6',
"fiscalYear" INTEGER NOT NULL DEFAULT 2025,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "budget_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sdgs_score" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"score" DOUBLE PRECISION NOT NULL DEFAULT 0,
"image" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "sdgs_score_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "satisfaction_rating" (
"id" TEXT NOT NULL,
"category" TEXT NOT NULL,
"value" INTEGER NOT NULL DEFAULT 0,
"color" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "satisfaction_rating_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "budget_category_fiscalYear_key" ON "budget"("category", "fiscalYear");
-- CreateIndex
CREATE UNIQUE INDEX "sdgs_score_title_key" ON "sdgs_score"("title");
-- CreateIndex
CREATE UNIQUE INDEX "satisfaction_rating_category_key" ON "satisfaction_rating"("category");

View File

@@ -31,7 +31,6 @@ model User {
innovationIdeas InnovationIdea[] @relation("IdeaReviewer")
healthRecords HealthRecord[]
populationDynamics PopulationDynamic[]
budgets Budget[]
budgetTransactions BudgetTransaction[]
posyandus Posyandu[]
securityReports SecurityReport[]
@@ -315,6 +314,46 @@ model Banjar {
@@map("banjar")
}
// --- KATEGORI 4: KEUANGAN & ANGGARAN ---
model Budget {
id String @id @default(cuid())
category String // "Belanja", "Pangan", "Pembiayaan", "Pendapatan"
amount Float @default(0)
percentage Float @default(0)
color String @default("#3B82F6")
fiscalYear Int @default(2025)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([category, fiscalYear])
@@map("budget")
}
// --- KATEGORI 5: METRIK DASHBOARD & SDGS ---
model SdgsScore {
id String @id @default(cuid())
title String @unique
score Float @default(0)
image String? // filename in public folder
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("sdgs_score")
}
model SatisfactionRating {
id String @id @default(cuid())
category String @unique // "Sangat Puas", "Puas", "Cukup", "Kurang"
value Int @default(0)
color String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("satisfaction_rating")
}
// --- STUBS FOR PHASE 2+ (To maintain relations) ---
model HealthRecord {
@@ -337,12 +376,6 @@ model PopulationDynamic {
documentor User @relation(fields: [documentedBy], references: [id])
}
model Budget {
id String @id @default(cuid())
approvedBy String?
approver User? @relation(fields: [approvedBy], references: [id])
}
model BudgetTransaction {
id String @id @default(cuid())
createdBy String

View File

@@ -1,327 +1,144 @@
import "dotenv/config";
import { hash } from "bcryptjs";
import { generateId } from "better-auth";
import { PrismaClient } from "../generated/prisma";
// Import all seeders
import { seedAdminUser, seedDemoUsers } from "./seeders/seed-auth";
import { seedBanjars, seedResidents, getBanjarIds } from "./seeders/seed-demographics";
import { seedDivisions, seedActivities, getDivisionIds } from "./seeders/seed-division-performance";
import {
ActivityStatus,
ComplaintCategory,
ComplaintStatus,
EventType,
Gender,
Priority,
PrismaClient,
Religion,
} from "../generated/prisma";
seedComplaints,
seedServiceLetters,
seedEvents,
seedInnovationIdeas,
} from "./seeders/seed-public-services";
import { seedDashboardMetrics } from "./seeders/seed-dashboard-metrics";
const prisma = new PrismaClient();
async function seedAdminUser() {
const adminEmail = process.env.ADMIN_EMAIL || "admin@example.com";
const adminPassword = process.env.ADMIN_PASSWORD || "admin123";
console.log(`Checking admin user: ${adminEmail}`);
const existingUser = await prisma.user.findUnique({
where: { email: adminEmail },
});
if (existingUser) {
if (existingUser.role !== "admin") {
await prisma.user.update({
where: { email: adminEmail },
data: { role: "admin" },
});
console.log("Updated existing user to admin role.");
}
return existingUser.id;
}
const hashedPassword = await hash(adminPassword, 12);
const userId = generateId();
await prisma.user.create({
data: {
id: userId,
email: adminEmail,
name: "Admin Desa Darmasaba",
role: "admin",
emailVerified: true,
accounts: {
create: {
id: generateId(),
accountId: userId,
providerId: "credential",
password: hashedPassword,
},
},
},
});
console.log(`Admin user created: ${adminEmail}`);
return userId;
}
async function seedBanjars() {
const banjars = [
{
name: "Darmasaba",
code: "DSB",
totalPopulation: 1200,
totalKK: 300,
totalPoor: 45,
},
{
name: "Manesa",
code: "MNS",
totalPopulation: 950,
totalKK: 240,
totalPoor: 32,
},
{
name: "Cabe",
code: "CBE",
totalPopulation: 800,
totalKK: 200,
totalPoor: 28,
},
{
name: "Penenjoan",
code: "PNJ",
totalPopulation: 1100,
totalKK: 280,
totalPoor: 50,
},
{
name: "Baler Pasar",
code: "BPS",
totalPopulation: 850,
totalKK: 210,
totalPoor: 35,
},
{
name: "Bucu",
code: "BCU",
totalPopulation: 734,
totalKK: 184,
totalPoor: 24,
},
];
console.log("Seeding Banjars...");
for (const banjar of banjars) {
await prisma.banjar.upsert({
where: { name: banjar.name },
update: banjar,
create: banjar,
});
}
}
async function seedDivisions() {
const divisions = [
{
name: "Pemerintahan",
description: "Urusan administrasi dan tata kelola desa",
color: "#1E3A5F",
},
{
name: "Pembangunan",
description: "Infrastruktur dan sarana prasarana desa",
color: "#2E7D32",
},
{
name: "Pemberdayaan",
description: "Pemberdayaan ekonomi dan masyarakat",
color: "#EF6C00",
},
{
name: "Kesejahteraan",
description: "Kesehatan, pendidikan, dan sosial",
color: "#C62828",
},
];
console.log("Seeding Divisions...");
const createdDivisions = [];
for (const div of divisions) {
const d = await prisma.division.upsert({
where: { name: div.name },
update: div,
create: div,
});
createdDivisions.push(d);
}
return createdDivisions;
}
async function seedResidents(banjarIds: string[]) {
console.log("Seeding Residents...");
const residents = [
{
nik: "5103010101700001",
kk: "5103010101700000",
name: "I Wayan Sudarsana",
birthDate: new Date("1970-05-15"),
birthPlace: "Badung",
gender: Gender.LAKI_LAKI,
religion: Religion.HINDU,
occupation: "Wiraswasta",
banjarId: banjarIds[0] || "",
rt: "001",
rw: "000",
address: "Jl. Raya Darmasaba No. 1",
isHeadOfHousehold: true,
},
{
nik: "5103010101850002",
kk: "5103010101850000",
name: "Ni Made Arianti",
birthDate: new Date("1985-08-20"),
birthPlace: "Denpasar",
gender: Gender.PEREMPUAN,
religion: Religion.HINDU,
occupation: "Guru",
banjarId: banjarIds[1] || banjarIds[0] || "",
rt: "002",
rw: "000",
address: "Gg. Manesa No. 5",
isPoor: true,
},
];
for (const res of residents) {
await prisma.resident.upsert({
where: { nik: res.nik },
update: res,
create: res,
});
}
}
async function seedActivities(divisionIds: string[]) {
console.log("Seeding Activities...");
const activities = [
{
title: "Rapat Koordinasi 2025",
description: "Penyusunan rencana kerja tahunan",
divisionId: divisionIds[0] || "",
progress: 100,
status: ActivityStatus.SELESAI,
priority: Priority.TINGGI,
},
{
title: "Pemutakhiran Indeks Desa",
description: "Pendataan SDG's Desa 2025",
divisionId: divisionIds[0] || "",
progress: 65,
status: ActivityStatus.BERJALAN,
priority: Priority.SEDANG,
},
{
title: "Pembangunan Jalan Banjar Cabe",
description: "Pengaspalan jalan utama",
divisionId: divisionIds[1] || divisionIds[0] || "",
progress: 40,
status: ActivityStatus.BERJALAN,
priority: Priority.DARURAT,
},
];
for (const act of activities) {
await prisma.activity.create({
data: act,
});
}
}
async function seedComplaints(adminId: string) {
console.log("Seeding Complaints...");
const complaints = [
{
complaintNumber: `COMP-20250326-001`,
title: "Lampu Jalan Mati",
description:
"Lampu jalan di depan Balai Banjar Manesa mati sejak 3 hari lalu.",
category: ComplaintCategory.INFRASTRUKTUR,
status: ComplaintStatus.BARU,
priority: Priority.SEDANG,
location: "Banjar Manesa",
reporterId: adminId,
},
{
complaintNumber: `COMP-20250326-002`,
title: "Sampah Menumpuk",
description: "Tumpukan sampah di area pasar Darmasaba belum diangkut.",
category: ComplaintCategory.KETERTIBAN_UMUM,
status: ComplaintStatus.DIPROSES,
priority: Priority.TINGGI,
location: "Pasar Darmasaba",
assignedTo: adminId,
},
];
for (const comp of complaints) {
await prisma.complaint.upsert({
where: { complaintNumber: comp.complaintNumber },
update: comp,
create: comp,
});
}
}
async function seedEvents(adminId: string) {
console.log("Seeding Events...");
const events = [
{
title: "Rapat Pleno Desa",
description: "Pembahasan anggaran belanja desa",
eventType: EventType.RAPAT,
startDate: new Date(),
location: "Balai Desa Darmasaba",
createdBy: adminId,
},
{
title: "Gotong Royong Kebersihan",
description: "Kegiatan rutin mingguan",
eventType: EventType.SOSIAL,
startDate: new Date(Date.now() + 86400000), // Besok
location: "Seluruh Banjar",
createdBy: adminId,
},
];
for (const event of events) {
await prisma.event.create({
data: event,
});
}
}
/**
* Run All Seeders
* Executes all seeder functions in the correct order
*/
export async function runSeed() {
console.log("Starting seed...");
console.log("🌱 Starting seed...\n");
// 1. Seed Authentication (Admin & Demo Users)
console.log("📁 [1/6] Authentication & Users");
const adminId = await seedAdminUser();
await seedBanjars();
const banjars = await prisma.banjar.findMany();
const banjarIds = banjars.map((b) => b.id);
await seedDemoUsers();
console.log();
// 2. Seed Demographics (Banjars & Residents)
console.log("📁 [2/6] Demographics & Population");
await seedBanjars();
const banjarIds = await getBanjarIds();
await seedResidents(banjarIds);
console.log();
// 3. Seed Division Performance (Divisions & Activities)
console.log("📁 [3/6] Division Performance");
const divisions = await seedDivisions();
const divisionIds = divisions.map((d) => d.id);
await seedResidents(banjarIds);
await seedActivities(divisionIds);
console.log();
// 4. Seed Public Services (Complaints, Service Letters, Events, Innovation)
console.log("📁 [4/6] Public Services");
await seedComplaints(adminId);
await seedServiceLetters(adminId);
await seedEvents(adminId);
await seedInnovationIdeas(adminId);
console.log();
console.log("Seed finished successfully!");
// 5. Seed Dashboard Metrics (Budget, SDGs, Satisfaction)
console.log("📁 [5/6] Dashboard Metrics");
await seedDashboardMetrics();
console.log();
console.log("✅ Seed finished successfully!\n");
}
if (import.meta.main) {
runSeed()
.catch((e) => {
console.error(e);
/**
* Run Specific Seeder
* Allows running individual seeders by name
*/
export async function runSpecificSeeder(name: string) {
console.log(`🌱 Running specific seeder: ${name}\n`);
switch (name) {
case "auth":
case "users":
console.log("📁 Authentication & Users");
await seedAdminUser();
await seedDemoUsers();
break;
case "demographics":
case "population":
console.log("📁 Demographics & Population");
await seedBanjars();
const banjarIds = await getBanjarIds();
await seedResidents(banjarIds);
break;
case "divisions":
case "performance":
console.log("📁 Division Performance");
const divisions = await seedDivisions();
const divisionIds = divisions.map((d) => d.id);
await seedActivities(divisionIds);
break;
case "complaints":
case "services":
case "public":
console.log("📁 Public Services");
const adminId = await seedAdminUser();
await seedComplaints(adminId);
await seedServiceLetters(adminId);
await seedEvents(adminId);
await seedInnovationIdeas(adminId);
break;
case "dashboard":
case "metrics":
console.log("📁 Dashboard Metrics");
await seedDashboardMetrics();
break;
default:
console.error(`❌ Unknown seeder: ${name}`);
console.log("Available seeders: auth, demographics, divisions, complaints, dashboard");
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
}
console.log("\n✅ Seeder finished successfully!\n");
}
// Main execution
if (import.meta.main) {
const args = process.argv.slice(2);
const seederName = args[0];
if (seederName) {
// Run specific seeder
runSpecificSeeder(seederName)
.catch((e) => {
console.error("❌ Seeder error:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
} else {
// Run all seeders
runSeed()
.catch((e) => {
console.error("❌ Seed error:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
}
}

119
prisma/seeders/seed-auth.ts Normal file
View File

@@ -0,0 +1,119 @@
import "dotenv/config";
import { hash } from "bcryptjs";
import { generateId } from "better-auth";
import { PrismaClient } from "../../generated/prisma";
const prisma = new PrismaClient();
/**
* Seed Admin User
* Creates or updates the admin user account
*/
export async function seedAdminUser() {
const adminEmail = process.env.ADMIN_EMAIL || "admin@example.com";
const adminPassword = process.env.ADMIN_PASSWORD || "admin123";
console.log(`Checking admin user: ${adminEmail}`);
const existingUser = await prisma.user.findUnique({
where: { email: adminEmail },
});
if (existingUser) {
if (existingUser.role !== "admin") {
await prisma.user.update({
where: { email: adminEmail },
data: { role: "admin" },
});
console.log("Updated existing user to admin role.");
}
return existingUser.id;
}
const hashedPassword = await hash(adminPassword, 12);
const userId = generateId();
await prisma.user.create({
data: {
id: userId,
email: adminEmail,
name: "Admin Desa Darmasaba",
role: "admin",
emailVerified: true,
accounts: {
create: {
id: generateId(),
accountId: userId,
providerId: "credential",
password: hashedPassword,
},
},
},
});
console.log(`✅ Admin user created: ${adminEmail}`);
return userId;
}
/**
* Seed Demo Users
* Creates demo users for testing (user, moderator roles)
*/
export async function seedDemoUsers() {
const demoUsers = [
{
email: "demo1@example.com",
name: "Demo User 1",
password: "demo123",
role: "user",
},
{
email: "demo2@example.com",
name: "Demo User 2",
password: "demo123",
role: "user",
},
{
email: "moderator@example.com",
name: "Moderator Desa",
password: "demo123",
role: "moderator",
},
];
console.log("Seeding Demo Users...");
for (const demo of demoUsers) {
const existingUser = await prisma.user.findUnique({
where: { email: demo.email },
});
if (existingUser) {
console.log(`⏭️ Demo user exists: ${demo.email}`);
continue;
}
const hashedPassword = await hash(demo.password, 12);
const userId = generateId();
await prisma.user.create({
data: {
id: userId,
email: demo.email,
name: demo.name,
role: demo.role,
emailVerified: true,
accounts: {
create: {
id: generateId(),
accountId: userId,
providerId: "credential",
password: hashedPassword,
},
},
},
});
console.log(`✅ Demo user created: ${demo.email}`);
}
}

View File

@@ -0,0 +1,109 @@
import { PrismaClient } from "../../generated/prisma";
const prisma = new PrismaClient();
/**
* Seed Budget (APBDes)
* Creates village budget allocation data
*/
export async function seedBudget() {
console.log("Seeding Budget...");
const budgets = [
{ category: "Belanja", amount: 70, percentage: 70, color: "#3B82F6" },
{ category: "Pangan", amount: 45, percentage: 45, color: "#22C55E" },
{ category: "Pembiayaan", amount: 55, percentage: 55, color: "#FACC15" },
{ category: "Pendapatan", amount: 90, percentage: 90, color: "#3B82F6" },
];
for (const budget of budgets) {
await prisma.budget.upsert({
where: {
category_fiscalYear: {
category: budget.category,
fiscalYear: 2025,
},
},
update: budget,
create: { ...budget, fiscalYear: 2025 },
});
}
console.log("✅ Budget seeded successfully");
}
/**
* Seed SDGs Scores
* Creates Sustainable Development Goals scores for dashboard
*/
export async function seedSdgsScores() {
console.log("Seeding SDGs Scores...");
const sdgs = [
{
title: "Desa Berenergi Bersih dan Terbarukan",
score: 99.64,
image: "SDGS-7.png",
},
{
title: "Desa Damai Berkeadilan",
score: 78.65,
image: "SDGS-16.png",
},
{
title: "Desa Sehat dan Sejahtera",
score: 77.37,
image: "SDGS-3.png",
},
{
title: "Desa Tanpa Kemiskinan",
score: 52.62,
image: "SDGS-1.png",
},
];
for (const sdg of sdgs) {
await prisma.sdgsScore.upsert({
where: { title: sdg.title },
update: sdg,
create: sdg,
});
}
console.log("✅ SDGs Scores seeded successfully");
}
/**
* Seed Satisfaction Ratings
* Creates public satisfaction survey data
*/
export async function seedSatisfactionRatings() {
console.log("Seeding Satisfaction Ratings...");
const satisfactions = [
{ category: "Sangat Puas", value: 25, color: "#4E5BA6" },
{ category: "Puas", value: 25, color: "#F4C542" },
{ category: "Cukup", value: 25, color: "#8CC63F" },
{ category: "Kurang", value: 25, color: "#E57373" },
];
for (const sat of satisfactions) {
await prisma.satisfactionRating.upsert({
where: { category: sat.category },
update: sat,
create: sat,
});
}
console.log("✅ Satisfaction Ratings seeded successfully");
}
/**
* Seed All Dashboard Metrics
* Main function to run all dashboard metrics seeders
*/
export async function seedDashboardMetrics() {
await seedBudget();
await seedSdgsScores();
await seedSatisfactionRatings();
}

View File

@@ -0,0 +1,124 @@
import { PrismaClient, Gender, Religion } from "../../generated/prisma";
const prisma = new PrismaClient();
/**
* Seed Banjars (Village Hamlets)
* Creates 6 banjars in Darmasaba village
*/
export async function seedBanjars() {
const banjars = [
{
name: "Darmasaba",
code: "DSB",
totalPopulation: 1200,
totalKK: 300,
totalPoor: 45,
},
{
name: "Manesa",
code: "MNS",
totalPopulation: 950,
totalKK: 240,
totalPoor: 32,
},
{
name: "Cabe",
code: "CBE",
totalPopulation: 800,
totalKK: 200,
totalPoor: 28,
},
{
name: "Penenjoan",
code: "PNJ",
totalPopulation: 1100,
totalKK: 280,
totalPoor: 50,
},
{
name: "Baler Pasar",
code: "BPS",
totalPopulation: 850,
totalKK: 210,
totalPoor: 35,
},
{
name: "Bucu",
code: "BCU",
totalPopulation: 734,
totalKK: 184,
totalPoor: 24,
},
];
console.log("Seeding Banjars...");
for (const banjar of banjars) {
await prisma.banjar.upsert({
where: { name: banjar.name },
update: banjar,
create: banjar,
});
}
console.log("✅ Banjars seeded successfully");
}
/**
* Get all Banjar IDs
* Helper function to retrieve banjar IDs for other seeders
*/
export async function getBanjarIds(): Promise<string[]> {
const banjars = await prisma.banjar.findMany();
return banjars.map((b) => b.id);
}
/**
* Seed Residents
* Creates sample resident data for demographics
*/
export async function seedResidents(banjarIds: string[]) {
console.log("Seeding Residents...");
const residents = [
{
nik: "5103010101700001",
kk: "5103010101700000",
name: "I Wayan Sudarsana",
birthDate: new Date("1970-05-15"),
birthPlace: "Badung",
gender: Gender.LAKI_LAKI,
religion: Religion.HINDU,
occupation: "Wiraswasta",
banjarId: banjarIds[0] || "",
rt: "001",
rw: "000",
address: "Jl. Raya Darmasaba No. 1",
isHeadOfHousehold: true,
},
{
nik: "5103010101850002",
kk: "5103010101850000",
name: "Ni Made Arianti",
birthDate: new Date("1985-08-20"),
birthPlace: "Denpasar",
gender: Gender.PEREMPUAN,
religion: Religion.HINDU,
occupation: "Guru",
banjarId: banjarIds[1] || banjarIds[0] || "",
rt: "002",
rw: "000",
address: "Gg. Manesa No. 5",
isPoor: true,
},
];
for (const res of residents) {
await prisma.resident.upsert({
where: { nik: res.nik },
update: res,
create: res,
});
}
console.log("✅ Residents seeded successfully");
}

View File

@@ -0,0 +1,101 @@
import {
ActivityStatus,
Priority,
PrismaClient,
} from "../../generated/prisma";
const prisma = new PrismaClient();
/**
* Seed Divisions
* Creates 4 main village divisions/departments
*/
export async function seedDivisions() {
const divisions = [
{
name: "Pemerintahan",
description: "Urusan administrasi dan tata kelola desa",
color: "#1E3A5F",
},
{
name: "Pembangunan",
description: "Infrastruktur dan sarana prasarana desa",
color: "#2E7D32",
},
{
name: "Pemberdayaan",
description: "Pemberdayaan ekonomi dan masyarakat",
color: "#EF6C00",
},
{
name: "Kesejahteraan",
description: "Kesehatan, pendidikan, dan sosial",
color: "#C62828",
},
];
console.log("Seeding Divisions...");
const createdDivisions = [];
for (const div of divisions) {
const d = await prisma.division.upsert({
where: { name: div.name },
update: div,
create: div,
});
createdDivisions.push(d);
}
console.log("✅ Divisions seeded successfully");
return createdDivisions;
}
/**
* Get all Division IDs
* Helper function to retrieve division IDs for other seeders
*/
export async function getDivisionIds(): Promise<string[]> {
const divisions = await prisma.division.findMany();
return divisions.map((d) => d.id);
}
/**
* Seed Activities
* Creates sample activities for each division
*/
export async function seedActivities(divisionIds: string[]) {
console.log("Seeding Activities...");
const activities = [
{
title: "Rapat Koordinasi 2025",
description: "Penyusunan rencana kerja tahunan",
divisionId: divisionIds[0] || "",
progress: 100,
status: ActivityStatus.SELESAI,
priority: Priority.TINGGI,
},
{
title: "Pemutakhiran Indeks Desa",
description: "Pendataan SDG's Desa 2025",
divisionId: divisionIds[0] || "",
progress: 65,
status: ActivityStatus.BERJALAN,
priority: Priority.SEDANG,
},
{
title: "Pembangunan Jalan Banjar Cabe",
description: "Pengaspalan jalan utama",
divisionId: divisionIds[1] || divisionIds[0] || "",
progress: 40,
status: ActivityStatus.BERJALAN,
priority: Priority.DARURAT,
},
];
for (const act of activities) {
await prisma.activity.create({
data: act,
});
}
console.log("✅ Activities seeded successfully");
}

View File

@@ -0,0 +1,174 @@
import {
ComplaintCategory,
ComplaintStatus,
EventType,
Priority,
PrismaClient,
} from "../../generated/prisma";
const prisma = new PrismaClient();
/**
* Seed Complaints
* Creates sample citizen complaints for testing
*/
export async function seedComplaints(adminId: string) {
console.log("Seeding Complaints...");
const complaints = [
{
complaintNumber: `COMP-20250326-001`,
title: "Lampu Jalan Mati",
description:
"Lampu jalan di depan Balai Banjar Manesa mati sejak 3 hari lalu.",
category: ComplaintCategory.INFRASTRUKTUR,
status: ComplaintStatus.BARU,
priority: Priority.SEDANG,
location: "Banjar Manesa",
reporterId: adminId,
},
{
complaintNumber: `COMP-20250326-002`,
title: "Sampah Menumpuk",
description: "Tumpukan sampah di area pasar Darmasaba belum diangkut.",
category: ComplaintCategory.KETERTIBAN_UMUM,
status: ComplaintStatus.DIPROSES,
priority: Priority.TINGGI,
location: "Pasar Darmasaba",
assignedTo: adminId,
},
];
for (const comp of complaints) {
await prisma.complaint.upsert({
where: { complaintNumber: comp.complaintNumber },
update: comp,
create: comp,
});
}
console.log("✅ Complaints seeded successfully");
}
/**
* Seed Service Letters
* Creates sample administrative letter requests
*/
export async function seedServiceLetters(adminId: string) {
console.log("Seeding Service Letters...");
const serviceLetters = [
{
letterNumber: "SKT-2025-001",
letterType: "KTP",
applicantName: "I Wayan Sudarsana",
applicantNik: "5103010101700001",
applicantAddress: "Jl. Raya Darmasaba No. 1",
purpose: "Pembuatan KTP baru",
status: "SELESAI",
processedBy: adminId,
completedAt: new Date(),
},
{
letterNumber: "SKT-2025-002",
letterType: "KK",
applicantName: "Ni Made Arianti",
applicantNik: "5103010101850002",
applicantAddress: "Gg. Manesa No. 5",
purpose: "Perubahan data KK",
status: "DIPROSES",
processedBy: adminId,
},
{
letterNumber: "SKT-2025-003",
letterType: "DOMISILI",
applicantName: "I Ketut Arsana",
applicantNik: "5103010101900003",
applicantAddress: "Jl. Cabe No. 10",
purpose: "Surat keterangan domisili",
status: "BARU",
},
];
for (const letter of serviceLetters) {
await prisma.serviceLetter.upsert({
where: { letterNumber: letter.letterNumber },
update: letter,
create: letter,
});
}
console.log("✅ Service Letters seeded successfully");
}
/**
* Seed Events
* Creates sample village events and meetings
*/
export async function seedEvents(adminId: string) {
console.log("Seeding Events...");
const events = [
{
title: "Rapat Pleno Desa",
description: "Pembahasan anggaran belanja desa",
eventType: EventType.RAPAT,
startDate: new Date(),
location: "Balai Desa Darmasaba",
createdBy: adminId,
},
{
title: "Gotong Royong Kebersihan",
description: "Kegiatan rutin mingguan",
eventType: EventType.SOSIAL,
startDate: new Date(Date.now() + 86400000), // Besok
location: "Seluruh Banjar",
createdBy: adminId,
},
];
for (const event of events) {
await prisma.event.create({
data: event,
});
}
console.log("✅ Events seeded successfully");
}
/**
* Seed Innovation Ideas
* Creates sample citizen innovation submissions
*/
export async function seedInnovationIdeas(adminId: string) {
console.log("Seeding Innovation Ideas...");
const innovationIdeas = [
{
title: "Sistem Informasi Desa Digital",
description: "Platform digital untuk layanan administrasi desa",
category: "Teknologi",
submitterName: "I Made Wijaya",
submitterContact: "081234567890",
status: "DIKAJI",
reviewedBy: adminId,
notes: "Perlu kajian lebih lanjut tentang anggaran",
},
{
title: "Program Bank Sampah",
description: "Pengelolaan sampah berbasis bank sampah",
category: "Lingkungan",
submitterName: "Ni Putu Sari",
submitterContact: "081234567891",
status: "BARU",
},
];
for (const idea of innovationIdeas) {
await prisma.innovationIdea.create({
data: idea,
});
}
console.log("✅ Innovation Ideas seeded successfully");
}