feat(noc): implement sync management UI and backend integration
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[externalId]` on the table `activity` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[externalId]` on the table `discussion` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[externalId]` on the table `division` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[externalId]` on the table `document` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[externalId]` on the table `event` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "activity" ADD COLUMN "externalId" TEXT,
|
||||
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "discussion" ADD COLUMN "externalId" TEXT,
|
||||
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "division" ADD COLUMN "externalId" TEXT,
|
||||
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "document" ADD COLUMN "externalId" TEXT,
|
||||
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "event" ADD COLUMN "externalId" TEXT,
|
||||
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "activity_externalId_key" ON "activity"("externalId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "discussion_externalId_key" ON "discussion"("externalId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "division_externalId_key" ON "division"("externalId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "document_externalId_key" ON "document"("externalId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "event_externalId_key" ON "event"("externalId");
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "division" ADD COLUMN "lastSyncedAt" TIMESTAMP(3);
|
||||
@@ -42,10 +42,13 @@ model User {
|
||||
|
||||
model Division {
|
||||
id String @id @default(cuid())
|
||||
externalId String? @unique // ID asli dari server NOC
|
||||
villageId String? @default("darmasaba") // ID Desa dari sistem NOC
|
||||
name String @unique
|
||||
description String?
|
||||
color String @default("#1E3A5F")
|
||||
isActive Boolean @default(true)
|
||||
lastSyncedAt DateTime? // Terakhir kali sinkronisasi dilakukan
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -59,6 +62,8 @@ model Division {
|
||||
|
||||
model Activity {
|
||||
id String @id @default(cuid())
|
||||
externalId String? @unique // ID asli dari server NOC
|
||||
villageId String? @default("darmasaba")
|
||||
title String
|
||||
description String?
|
||||
divisionId String
|
||||
@@ -82,6 +87,8 @@ model Activity {
|
||||
|
||||
model Document {
|
||||
id String @id @default(cuid())
|
||||
externalId String? @unique // ID asli dari server NOC
|
||||
villageId String? @default("darmasaba")
|
||||
title String
|
||||
category DocumentCategory
|
||||
type String // "Gambar", "Dokumen", "PDF", etc
|
||||
@@ -101,6 +108,8 @@ model Document {
|
||||
|
||||
model Discussion {
|
||||
id String @id @default(cuid())
|
||||
externalId String? @unique // ID asli dari server NOC
|
||||
villageId String? @default("darmasaba")
|
||||
message String
|
||||
senderId String
|
||||
parentId String? // For threaded discussions
|
||||
@@ -121,6 +130,8 @@ model Discussion {
|
||||
|
||||
model Event {
|
||||
id String @id @default(cuid())
|
||||
externalId String? @unique // ID asli dari server NOC
|
||||
villageId String? @default("darmasaba")
|
||||
title String
|
||||
description String?
|
||||
eventType EventType
|
||||
|
||||
@@ -2,20 +2,32 @@ import "dotenv/config";
|
||||
import { PrismaClient } from "../generated/prisma";
|
||||
|
||||
// Import all seeders
|
||||
import { seedAdminUser, seedDemoUsers, seedApiKeys } from "./seeders/seed-auth";
|
||||
import { seedBanjars, seedResidents, getBanjarIds } from "./seeders/seed-demographics";
|
||||
import { seedDivisions, seedActivities, getDivisionIds } from "./seeders/seed-division-performance";
|
||||
import { seedAdminUser, seedApiKeys, seedDemoUsers } from "./seeders/seed-auth";
|
||||
import { seedDashboardMetrics } from "./seeders/seed-dashboard-metrics";
|
||||
import {
|
||||
getBanjarIds,
|
||||
seedBanjars,
|
||||
seedResidents,
|
||||
} from "./seeders/seed-demographics";
|
||||
import {
|
||||
seedDiscussions,
|
||||
seedDivisionMetrics,
|
||||
seedDocuments,
|
||||
} from "./seeders/seed-discussions";
|
||||
import {
|
||||
getDivisionIds,
|
||||
seedActivities,
|
||||
seedDivisions,
|
||||
} from "./seeders/seed-division-performance";
|
||||
import { seedPhase2 } from "./seeders/seed-phase2";
|
||||
import {
|
||||
getComplaintIds,
|
||||
seedComplaints,
|
||||
seedServiceLetters,
|
||||
seedComplaintUpdates,
|
||||
seedEvents,
|
||||
seedInnovationIdeas,
|
||||
seedComplaintUpdates,
|
||||
getComplaintIds,
|
||||
seedServiceLetters,
|
||||
} from "./seeders/seed-public-services";
|
||||
import { seedDocuments, seedDiscussions, seedDivisionMetrics } from "./seeders/seed-discussions";
|
||||
import { seedDashboardMetrics } from "./seeders/seed-dashboard-metrics";
|
||||
import { seedPhase2 } from "./seeders/seed-phase2";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -45,7 +57,9 @@ export async function runSeed() {
|
||||
// Check if data already exists
|
||||
const existingData = await hasExistingData();
|
||||
if (existingData) {
|
||||
console.log("⏭️ Existing data detected. Skipping seed to prevent duplicates.\n");
|
||||
console.log(
|
||||
"⏭️ Existing data detected. Skipping seed to prevent duplicates.\n",
|
||||
);
|
||||
console.log("💡 To re-seed, either:");
|
||||
console.log(" 1. Run: bun x prisma migrate reset (resets database)");
|
||||
console.log(" 2. Manually delete data from tables\n");
|
||||
@@ -114,7 +128,9 @@ export async function runSpecificSeeder(name: string) {
|
||||
// Check if data already exists for specific seeder
|
||||
const existingData = await hasExistingData();
|
||||
if (existingData && name !== "auth") {
|
||||
console.log("⚠️ Warning: Existing data detected for this seeder category.\n");
|
||||
console.log(
|
||||
"⚠️ Warning: Existing data detected for this seeder category.\n",
|
||||
);
|
||||
console.log("💡 To re-seed, either:");
|
||||
console.log(" 1. Run: bun x prisma migrate reset (resets database)");
|
||||
console.log(" 2. Manually delete data from tables\n");
|
||||
@@ -124,33 +140,36 @@ export async function runSpecificSeeder(name: string) {
|
||||
|
||||
switch (name) {
|
||||
case "auth":
|
||||
case "users":
|
||||
case "users": {
|
||||
console.log("📁 Authentication & Users");
|
||||
const adminId = await seedAdminUser();
|
||||
await seedDemoUsers();
|
||||
await seedApiKeys(adminId);
|
||||
break;
|
||||
}
|
||||
|
||||
case "demographics":
|
||||
case "population":
|
||||
case "population": {
|
||||
console.log("📁 Demographics & Population");
|
||||
await seedBanjars();
|
||||
const banjarIds = await getBanjarIds();
|
||||
await seedResidents(banjarIds);
|
||||
break;
|
||||
}
|
||||
|
||||
case "divisions":
|
||||
case "performance":
|
||||
case "performance": {
|
||||
console.log("📁 Division Performance");
|
||||
const divisions = await seedDivisions();
|
||||
const divisionIds = divisions.map((d) => d.id);
|
||||
await seedActivities(divisionIds);
|
||||
await seedDivisionMetrics(divisionIds);
|
||||
break;
|
||||
}
|
||||
|
||||
case "complaints":
|
||||
case "services":
|
||||
case "public":
|
||||
case "public": {
|
||||
console.log("📁 Public Services");
|
||||
const pubAdminId = await seedAdminUser();
|
||||
await seedComplaints(pubAdminId);
|
||||
@@ -160,9 +179,10 @@ export async function runSpecificSeeder(name: string) {
|
||||
const compIds = await getComplaintIds();
|
||||
await seedComplaintUpdates(compIds, pubAdminId);
|
||||
break;
|
||||
}
|
||||
|
||||
case "documents":
|
||||
case "discussions":
|
||||
case "discussions": {
|
||||
console.log("📁 Documents & Discussions");
|
||||
const docAdminId = await seedAdminUser();
|
||||
const divs = await seedDivisions();
|
||||
@@ -170,6 +190,7 @@ export async function runSpecificSeeder(name: string) {
|
||||
await seedDocuments(divIds, docAdminId);
|
||||
await seedDiscussions(divIds, docAdminId);
|
||||
break;
|
||||
}
|
||||
|
||||
case "dashboard":
|
||||
case "metrics":
|
||||
@@ -178,17 +199,20 @@ export async function runSpecificSeeder(name: string) {
|
||||
break;
|
||||
|
||||
case "phase2":
|
||||
case "features":
|
||||
case "features": {
|
||||
console.log("📁 Phase 2+ Features");
|
||||
const p2AdminId = await seedAdminUser();
|
||||
await seedBanjars();
|
||||
const p2BanjarIds = await getBanjarIds();
|
||||
await seedPhase2(p2BanjarIds, p2AdminId);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.error(`❌ Unknown seeder: ${name}`);
|
||||
console.log("Available seeders: auth, demographics, divisions, complaints, documents, dashboard, phase2");
|
||||
console.log(
|
||||
"Available seeders: auth, demographics, divisions, complaints, documents, dashboard, phase2",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient, Gender, Religion } from "../../generated/prisma";
|
||||
import { Gender, PrismaClient, Religion } from "../../generated/prisma";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
ActivityStatus,
|
||||
Priority,
|
||||
PrismaClient,
|
||||
} from "../../generated/prisma";
|
||||
import { ActivityStatus, Priority, PrismaClient } from "../../generated/prisma";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
|
||||
@@ -25,13 +25,14 @@ export async function seedComplaints(adminId: string) {
|
||||
console.log("Seeding Complaints...");
|
||||
|
||||
const now = new Date();
|
||||
|
||||
|
||||
const complaints = [
|
||||
// Recent complaints (this month)
|
||||
{
|
||||
complaintNumber: `COMP-20260327-001`,
|
||||
title: "Lampu Jalan Mati",
|
||||
description: "Lampu jalan di depan Balai Banjar Manesa mati sejak 3 hari lalu.",
|
||||
description:
|
||||
"Lampu jalan di depan Balai Banjar Manesa mati sejak 3 hari lalu.",
|
||||
category: ComplaintCategory.INFRASTRUKTUR,
|
||||
status: ComplaintStatus.BARU,
|
||||
priority: Priority.SEDANG,
|
||||
@@ -187,7 +188,9 @@ export async function seedComplaints(adminId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log("✅ Complaints seeded successfully (12 complaints across 7 months)");
|
||||
console.log(
|
||||
"✅ Complaints seeded successfully (12 complaints across 7 months)",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +357,10 @@ export async function seedInnovationIdeas(adminId: string) {
|
||||
* Seed Complaint Updates
|
||||
* Creates status update history for complaints
|
||||
*/
|
||||
export async function seedComplaintUpdates(complaintIds: string[], userId: string) {
|
||||
export async function seedComplaintUpdates(
|
||||
complaintIds: string[],
|
||||
userId: string,
|
||||
) {
|
||||
console.log("Seeding Complaint Updates...");
|
||||
|
||||
if (complaintIds.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user