add shema for map fitur

This commit is contained in:
vergiLgood1 2025-02-21 06:03:47 +07:00
parent a0b0289a86
commit 538a8f4fa8
13 changed files with 1239 additions and 136 deletions

View File

@ -151,12 +151,12 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
title: item.title,
url: item.url,
icon: item.icon,
isActive: item.is_active,
items: item.sub_items.map((subItem) => ({
isActive: item.isActive,
items: item.subItems.map((subItem) => ({
title: subItem.title,
url: subItem.url,
icon: subItem.icon,
isActive: subItem.is_active,
isActive: subItem.isActive,
})),
}));
}, [navItems]);

View File

@ -0,0 +1,179 @@
/*
Warnings:
- Added the required column `slug` to the `nav_items` table without a default value. This is not possible if the table is not empty.
- Added the required column `icon` to the `nav_sub_items` table without a default value. This is not possible if the table is not empty.
- Added the required column `slug` to the `nav_sub_items` table without a default value. This is not possible if the table is not empty.
*/
-- CreateEnum
CREATE TYPE "crime_rates" AS ENUM ('low', 'medium', 'high');
-- CreateEnum
CREATE TYPE "crime_status" AS ENUM ('new', 'in_progress', 'resolved');
-- AlterTable
ALTER TABLE "contact_messages" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" DROP DEFAULT;
-- AlterTable
ALTER TABLE "nav_items" ADD COLUMN "slug" VARCHAR(255) NOT NULL,
ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" DROP DEFAULT;
-- AlterTable
ALTER TABLE "nav_sub_items" ADD COLUMN "icon" VARCHAR(100) NOT NULL,
ADD COLUMN "is_active" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "slug" VARCHAR(255) NOT NULL,
ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" DROP DEFAULT;
-- CreateTable
CREATE TABLE "cities" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"geographic_id" UUID,
"name" VARCHAR(100) NOT NULL,
"code" VARCHAR(10) NOT NULL,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
CONSTRAINT "cities_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "districts" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"city_id" UUID NOT NULL,
"name" VARCHAR(100) NOT NULL,
"code" VARCHAR(10) NOT NULL,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
CONSTRAINT "districts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "geographics" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"district_id" UUID,
"latitude" DOUBLE PRECISION,
"longitude" DOUBLE PRECISION,
"land_area" DOUBLE PRECISION,
"polygon" JSONB,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
CONSTRAINT "geographics_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "demographics" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"district_id" UUID,
"city_id" UUID,
"province_id" UUID,
"year" INTEGER NOT NULL,
"population" INTEGER NOT NULL,
"population_density" DOUBLE PRECISION NOT NULL,
"poverty_rate" DOUBLE PRECISION NOT NULL,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
CONSTRAINT "demographics_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "crimes" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"district_id" UUID,
"city_id" UUID,
"year" INTEGER NOT NULL,
"number_of_crime" INTEGER NOT NULL,
"rate" "crime_rates" NOT NULL DEFAULT 'low',
"heat_map" JSONB,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
CONSTRAINT "crimes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "crime_cases" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"crime_id" UUID,
"crime_category_id" UUID,
"date" TIMESTAMPTZ(6) NOT NULL,
"time" TIMESTAMPTZ(6) NOT NULL,
"location" VARCHAR(255) NOT NULL,
"latitude" DOUBLE PRECISION NOT NULL,
"longitude" DOUBLE PRECISION NOT NULL,
"description" TEXT NOT NULL,
"victim_count" INTEGER NOT NULL,
"status" "crime_status" NOT NULL DEFAULT 'new',
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
CONSTRAINT "crime_cases_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "crime_categories" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"name" VARCHAR(255) NOT NULL,
"description" TEXT NOT NULL,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
CONSTRAINT "crime_categories_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "cities_name_idx" ON "cities"("name");
-- CreateIndex
CREATE INDEX "districts_name_idx" ON "districts"("name");
-- CreateIndex
CREATE UNIQUE INDEX "geographics_district_id_key" ON "geographics"("district_id");
-- CreateIndex
CREATE UNIQUE INDEX "demographics_district_id_year_key" ON "demographics"("district_id", "year");
-- CreateIndex
CREATE UNIQUE INDEX "demographics_city_id_year_key" ON "demographics"("city_id", "year");
-- CreateIndex
CREATE UNIQUE INDEX "crimes_district_id_year_key" ON "crimes"("district_id", "year");
-- CreateIndex
CREATE UNIQUE INDEX "crimes_city_id_year_key" ON "crimes"("city_id", "year");
-- CreateIndex
CREATE INDEX "profiles_user_id_idx" ON "profiles"("user_id");
-- AddForeignKey
ALTER TABLE "cities" ADD CONSTRAINT "cities_geographic_id_fkey" FOREIGN KEY ("geographic_id") REFERENCES "geographics"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "districts" ADD CONSTRAINT "districts_city_id_fkey" FOREIGN KEY ("city_id") REFERENCES "cities"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "geographics" ADD CONSTRAINT "geographics_district_id_fkey" FOREIGN KEY ("district_id") REFERENCES "districts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "demographics" ADD CONSTRAINT "demographics_district_id_fkey" FOREIGN KEY ("district_id") REFERENCES "districts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "demographics" ADD CONSTRAINT "demographics_city_id_fkey" FOREIGN KEY ("city_id") REFERENCES "cities"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "crimes" ADD CONSTRAINT "crimes_district_id_fkey" FOREIGN KEY ("district_id") REFERENCES "districts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "crimes" ADD CONSTRAINT "crimes_city_id_fkey" FOREIGN KEY ("city_id") REFERENCES "cities"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "crime_cases" ADD CONSTRAINT "crime_cases_crime_id_fkey" FOREIGN KEY ("crime_id") REFERENCES "crimes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "crime_cases" ADD CONSTRAINT "crime_cases_crime_category_id_fkey" FOREIGN KEY ("crime_category_id") REFERENCES "crime_categories"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -0,0 +1,63 @@
-- AlterTable
ALTER TABLE "cities" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "contact_messages" ALTER COLUMN "created_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "crime_cases" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "crime_categories" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "crimes" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "demographics" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "districts" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "geographics" ALTER COLUMN "created_at" SET DEFAULT now(),
ALTER COLUMN "updated_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "nav_items" ALTER COLUMN "created_at" SET DEFAULT now();
-- AlterTable
ALTER TABLE "nav_sub_items" ALTER COLUMN "created_at" SET DEFAULT now();
-- CreateTable
CREATE TABLE "nav_sub_sub_items" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"title" VARCHAR(255) NOT NULL,
"url" VARCHAR(255) NOT NULL,
"slug" VARCHAR(255) NOT NULL,
"icon" VARCHAR(100) NOT NULL,
"is_active" BOOLEAN NOT NULL DEFAULT false,
"order_seq" INTEGER NOT NULL,
"nav_sub_item_id" UUID NOT NULL,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
"updated_at" TIMESTAMPTZ(6) NOT NULL,
"created_by" UUID,
"updated_by" UUID,
CONSTRAINT "nav_sub_sub_items_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "nav_sub_sub_items_nav_sub_item_id_idx" ON "nav_sub_sub_items"("nav_sub_item_id");
-- CreateIndex
CREATE INDEX "nav_sub_sub_items_title_idx" ON "nav_sub_sub_items"("title");
-- AddForeignKey
ALTER TABLE "nav_sub_sub_items" ADD CONSTRAINT "nav_sub_sub_items_nav_sub_item_id_fkey" FOREIGN KEY ("nav_sub_item_id") REFERENCES "nav_sub_items"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -17,18 +17,18 @@ datasource db {
}
model User {
id String @id @db.Uuid
email String @unique @db.VarChar(255)
email_verified Boolean @default(false)
password String? @db.VarChar(255)
first_name String? @db.VarChar(255)
last_name String? @db.VarChar(255)
avatar String? @db.VarChar(255)
role Role @default(user)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
last_signed_in DateTime?
metadata Json?
id String @id @db.Uuid
email String @unique @db.VarChar(255)
emailVerified Boolean @default(false) @map("email_verified")
password String? @db.VarChar(255)
firstName String? @map("first_name") @db.VarChar(255)
lastName String? @map("last_name") @db.VarChar(255)
avatar String? @db.VarChar(255)
role Role @default(user)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
lastSignedIn DateTime? @map("last_signed_in")
metadata Json?
profile Profile?
@ -37,75 +37,237 @@ model User {
}
model Profile {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
user_id String @unique @db.Uuid
bio String? @db.Text
phone String? @db.VarChar(20)
address String? @db.VarChar(255)
city String? @db.VarChar(100)
country String? @db.VarChar(100)
birth_date DateTime?
user User @relation(fields: [user_id], references: [id])
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
userId String @unique @map("user_id") @db.Uuid
bio String? @db.Text
phone String? @db.VarChar(20)
address String? @db.VarChar(255)
city String? @db.VarChar(100)
country String? @db.VarChar(100)
birthDate DateTime? @map("birth_date")
user User @relation(fields: [userId], references: [id])
@@index([user_id])
@@index([userId])
@@map("profiles") // Maps to Supabase's 'profiles' table
}
model ContactMessages {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String? @db.VarChar(255)
email String? @db.VarChar(255)
phone String? @db.VarChar(20)
message_type String? @db.VarChar(50)
message_type_label String? @db.VarChar(50)
message String? @db.Text
status StatusContactMessages @default(new)
created_at DateTime @default(dbgenerated("now()")) @db.Timestamptz(6)
updated_at DateTime @default(dbgenerated("now()")) @updatedAt @db.Timestamptz(6)
model ContactMessage {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String? @db.VarChar(255)
email String? @db.VarChar(255)
phone String? @db.VarChar(20)
messageType String? @map("message_type") @db.VarChar(50)
messageTypeLabel String? @map("message_type_label") @db.VarChar(50)
message String? @db.Text
status StatusContactMessages @default(new)
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
@@map("contact_messages") // Maps to Supabase's 'contact_messages' table
}
model NavItems {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
title String @db.VarChar(255)
url String @db.VarChar(255)
slug String @db.VarChar(255)
icon String @db.VarChar(100)
is_active Boolean @default(false)
order_seq Int
created_at DateTime @default(dbgenerated("now()")) @db.Timestamptz(6)
updated_at DateTime @default(dbgenerated("now()")) @updatedAt @db.Timestamptz(6)
sub_items NavSubItems[]
created_by String? @db.Uuid
updated_by String? @db.Uuid
model NavItem {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
title String @db.VarChar(255)
url String @db.VarChar(255)
slug String @db.VarChar(255)
icon String @db.VarChar(100)
isActive Boolean @default(false) @map("is_active")
orderSeq Int @map("order_seq")
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
subItems NavSubItem[]
createdBy String? @map("created_by") @db.Uuid
updatedBy String? @map("updated_by") @db.Uuid
@@index([title])
@@index([is_active])
@@index([isActive])
@@map("nav_items")
}
model NavSubItems {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
title String @db.VarChar(255)
url String @db.VarChar(255)
slug String @db.VarChar(255)
icon String @db.VarChar(100)
is_active Boolean @default(false)
order_seq Int
nav_item_id String @db.Uuid
created_at DateTime @default(dbgenerated("now()")) @db.Timestamptz(6)
updated_at DateTime @default(dbgenerated("now()")) @updatedAt @db.Timestamptz(6)
created_by String? @db.Uuid
updated_by String? @db.Uuid
nav_item NavItems @relation(fields: [nav_item_id], references: [id], onDelete: Cascade)
model NavSubItem {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
title String @db.VarChar(255)
url String @db.VarChar(255)
slug String @db.VarChar(255)
icon String @db.VarChar(100)
isActive Boolean @default(false) @map("is_active")
orderSeq Int @map("order_seq")
navItemId String @map("nav_item_id") @db.Uuid
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
createdBy String? @map("created_by") @db.Uuid
updatedBy String? @map("updated_by") @db.Uuid
navItem NavItem @relation(fields: [navItemId], references: [id], onDelete: Cascade)
NavSubSubItem NavSubSubItem[]
@@index([nav_item_id])
@@index([navItemId])
@@index([title])
@@map("nav_sub_items")
}
model NavSubSubItem {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
title String @db.VarChar(255)
url String @db.VarChar(255)
slug String @db.VarChar(255)
icon String @db.VarChar(100)
isActive Boolean @default(false) @map("is_active")
orderSeq Int @map("order_seq")
navSubItemId String @map("nav_sub_item_id") @db.Uuid
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
createdBy String? @map("created_by") @db.Uuid
updatedBy String? @map("updated_by") @db.Uuid
navSubItem NavSubItem @relation(fields: [navSubItemId], references: [id], onDelete: Cascade)
@@index([navSubItemId])
@@index([title])
@@map("nav_sub_sub_items")
}
model NavigationItem {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
title String @db.VarChar(255)
url String? @db.VarChar(255)
slug String @db.VarChar(255)
icon String? @db.VarChar(100)
path String @unique @db.VarChar(255) // Materialized path (e.g., "1.2.3")
level Int @default(0)
isActive Boolean @default(false) @map("is_active")
orderSeq Int @map("order_seq")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
createdBy String? @map("created_by") @db.Uuid
updatedBy String? @map("updated_by") @db.Uuid
@@index([path])
@@index([level])
@@index([isActive])
@@map("navigation_items")
}
model City {
id String @id @default(dbgenerated("gen_random_uuid()")) @map("id") @db.Uuid
geographicId String? @map("geographic_id") @db.Uuid
name String @map("name") @db.VarChar(100)
code String @map("code") @db.VarChar(10)
demographics Demographic[]
crimes Crime[]
districts District[]
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @map("updated_at") @db.Timestamptz(6)
geographic Geographic? @relation(fields: [geographicId], references: [id])
@@index([name])
@@map("cities")
}
model District {
id String @id @default(dbgenerated("gen_random_uuid()")) @map("id") @db.Uuid
cityId String @map("city_id") @db.Uuid
name String @map("name") @db.VarChar(100)
code String @map("code") @db.VarChar(10)
geographic Geographic?
demographics Demographic[]
crimes Crime[]
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @map("updated_at") @db.Timestamptz(6)
city City @relation(fields: [cityId], references: [id], onDelete: Cascade)
@@index([name])
@@map("districts")
}
model Geographic {
id String @id @default(dbgenerated("gen_random_uuid()")) @map("id") @db.Uuid
districtId String? @unique @map("district_id") @db.Uuid
latitude Float? @map("latitude")
longitude Float? @map("longitude")
landArea Float? @map("land_area")
polygon Json? @map("polygon")
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @map("updated_at") @db.Timestamptz(6)
district District? @relation(fields: [districtId], references: [id])
cities City[]
@@map("geographics")
}
model Demographic {
id String @id @default(dbgenerated("gen_random_uuid()")) @map("id") @db.Uuid
districtId String? @map("district_id") @db.Uuid
cityId String? @map("city_id") @db.Uuid
provinceId String? @map("province_id") @db.Uuid
year Int @map("year")
population Int @map("population")
populationDensity Float @map("population_density")
povertyRate Float @map("poverty_rate")
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @map("updated_at") @db.Timestamptz(6)
district District? @relation(fields: [districtId], references: [id])
city City? @relation(fields: [cityId], references: [id])
@@unique([districtId, year])
@@unique([cityId, year])
@@map("demographics")
}
model Crime {
id String @id @default(dbgenerated("gen_random_uuid()")) @map("id") @db.Uuid
districtId String? @map("district_id") @db.Uuid
cityId String? @map("city_id") @db.Uuid
year Int @map("year")
numberOfCrime Int @map("number_of_crime")
rate CrimeRate @default(low) @map("rate")
heatMap Json? @map("heat_map")
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @map("updated_at") @db.Timestamptz(6)
district District? @relation(fields: [districtId], references: [id])
city City? @relation(fields: [cityId], references: [id])
crimeCases CrimeCase[]
@@unique([districtId, year])
@@unique([cityId, year])
@@map("crimes")
}
model CrimeCase {
id String @id @default(dbgenerated("gen_random_uuid()")) @map("id") @db.Uuid
crimeId String? @map("crime_id") @db.Uuid
crimeCategoryId String? @map("crime_category_id") @db.Uuid
date DateTime @map("date") @db.Timestamptz(6)
time DateTime @map("time") @db.Timestamptz(6)
location String @map("location") @db.VarChar(255)
latitude Float @map("latitude")
longitude Float @map("longitude")
description String @map("description") @db.Text
victimCount Int @map("victim_count")
status CrimeStatus @default(new) @map("status")
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @map("updated_at") @db.Timestamptz(6)
crime Crime? @relation(fields: [crimeId], references: [id])
crimeCategory CrimeCategory? @relation(fields: [crimeCategoryId], references: [id])
@@map("crime_cases")
}
model CrimeCategory {
id String @id @default(dbgenerated("gen_random_uuid()")) @map("id") @db.Uuid
name String @map("name") @db.VarChar(255)
description String @map("description") @db.Text
crimeCases CrimeCase[]
createdAt DateTime @default(dbgenerated("now()")) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @map("updated_at") @db.Timestamptz(6)
@@map("crime_categories")
}
enum Role {
admin
@ -123,3 +285,19 @@ enum StatusContactMessages {
@@map("status_contact_messages")
}
enum CrimeRate {
low
medium
high
@@map("crime_rates")
}
enum CrimeStatus {
new
inProgress @map("in_progress")
resolved
@@map("crime_status")
}

View File

@ -1,61 +1,294 @@
const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient();
// Navigation data structure
const navItemDatas = [
{
title: "Dashboard",
url: "/dashboard",
slug: "dashboard",
orderSeq: 1,
icon: "IconHome",
isActive: true,
subItems: [],
},
{
title: "Crime Management",
url: "/crime-management",
slug: "crime-management",
orderSeq: 2,
icon: "IconAlertTriangle",
isActive: true,
subItems: [
{
title: "Crime Overview",
url: "/crime-management/crime-overview",
slug: "crime-overview",
icon: "IconAlertTriangle",
orderSeq: 1,
isActive: true,
},
{
title: "Crime Categories",
url: "/crime-management/crime-categories",
slug: "crime-categories",
icon: "IconSettings",
orderSeq: 2,
isActive: true,
},
{
title: "Cases",
url: "/crime-management/crime-cases",
slug: "crime-cases",
icon: "IconAlertTriangle",
orderSeq: 3,
isActive: true,
subItems: [
{
title: "New Case",
url: "/crime-management/crime-cases/case-new",
slug: "new-case",
icon: "IconAlertTriangle",
orderSeq: 1,
isActive: true,
},
{
title: "Active Cases",
url: "/crime-management/crime-cases/case-active",
slug: "active-cases",
icon: "IconAlertTriangle",
orderSeq: 2,
isActive: true,
},
{
title: "Resolved Cases",
url: "/crime-management/crime-cases/case-closed",
slug: "resolved-cases",
icon: "IconAlertTriangle",
orderSeq: 3,
isActive: true,
},
],
},
],
},
{
title: "Geographic Data",
url: "/geographic-data",
slug: "geographic-data",
orderSeq: 3,
icon: "IconMap",
isActive: true,
subItems: [
{
title: "Locations",
url: "/geographic-data/locations",
slug: "locations",
icon: "IconMap",
orderSeq: 1,
isActive: true,
subItems: [
{
title: "Cities",
url: "/geographic-data/cities",
slug: "cities",
icon: "IconMap",
orderSeq: 1,
isActive: true,
},
{
title: "Districts",
url: "/geographic-data/districts",
slug: "districts",
icon: "IconMap",
orderSeq: 2,
isActive: true,
},
],
},
{
title: "Geographic Info",
url: "/geographic-data/geographic-info",
slug: "geographic-info",
icon: "IconMap",
orderSeq: 3,
isActive: true,
},
],
},
{
title: "Demographics",
url: "/demographics",
slug: "demographics",
orderSeq: 4,
icon: "IconDatabase",
isActive: true,
subItems: [
{
title: "Demographics Data",
url: "/demographics/demographics-data",
slug: "demographics-data",
icon: "IconDatabase",
orderSeq: 1,
isActive: true,
},
],
},
{
title: "User Management",
url: "/user-management",
slug: "user-management",
orderSeq: 5,
icon: "IconUsers",
isActive: true,
subItems: [
{
title: "Users",
url: "/user-management/users",
slug: "users",
icon: "IconUsers",
orderSeq: 1,
isActive: true,
},
],
},
{
title: "Communication",
url: "/communication",
slug: "communication",
orderSeq: 6,
icon: "IconMessageCircle",
isActive: true,
subItems: [
{
title: "Contact Messages",
url: "/communication/contact-messages",
slug: "contact-messages",
icon: "IconMessageCircle",
orderSeq: 1,
isActive: true,
},
],
},
{
title: "Settings",
url: "/settings",
slug: "settings",
orderSeq: 7,
icon: "IconSettings",
isActive: true,
subItems: [
{
title: "Navigation",
url: "/settings/navigation",
slug: "navigation",
icon: "IconMenu2",
orderSeq: 1,
isActive: true,
subItems: [
{
title: "Nav Items",
url: "/settings/navigation/nav-items",
slug: "nav-items",
icon: "IconMenu2",
orderSeq: 1,
isActive: true,
subSubItems: [
{
title: "Nav Sub Items",
url: "/settings/navigation/nav-sub-items",
slug: "nav-sub-items",
icon: "IconMenu2",
orderSeq: 1,
isActive: true,
},
],
},
],
},
],
},
];
// Helper function to convert icon names
const convertIconName = (iconName: string): string => {
return iconName.replace("Icon", "").toLowerCase();
};
// Helper function to create path
const createPath = (currentPath: string, orderSeq: number): string => {
return currentPath ? `${currentPath}.${orderSeq}` : `${orderSeq}`;
};
// Helper function to calculate level from path
const calculateLevel = (path: string): number => {
return path.split(".").length - 1;
};
// Helper function to process navigation items recursively
const processNavigationItems = (
items: any[],
parentPath: string = ""
): any[] => {
const processed: any[] = [];
items.forEach((item) => {
const currentPath = createPath(parentPath, item.orderSeq);
const navigationItem = {
title: item.title,
url: item.url,
slug: item.slug,
icon: convertIconName(item.icon),
path: currentPath,
level: calculateLevel(currentPath),
isActive: item.isActive,
orderSeq: item.orderSeq,
};
processed.push(navigationItem);
// Process subItems if they exist
if (item.subItems && item.subItems.length > 0) {
const subItems = processNavigationItems(item.subItems, currentPath);
processed.push(...subItems);
}
// Process subSubItems if they exist (for backward compatibility)
if (item.subSubItems && item.subSubItems.length > 0) {
const subSubItems = processNavigationItems(item.subSubItems, currentPath);
processed.push(...subSubItems);
}
});
return processed;
};
async function main() {
// Clear existing data
await prisma.navSubItems.deleteMany({});
await prisma.navItems.deleteMany({});
// First, delete all existing navigation items
await prisma.navSubItem.deleteMany({});
await prisma.navItem.deleteMany({});
await prisma.navigationItem.deleteMany({});
const navItemDatas = [
{
title: "Dashboard",
url: "/dashboard",
slug: "dashboard",
order_seq: 1,
icon: "LayoutDashboard",
sub_items: [],
},
{
title: "Master",
url: "/master",
slug: "master",
order_seq: 2,
icon: "IconDashboard",
sub_items: [
{
title: "Users",
url: "/master/users",
slug: "users",
icon: "IconUsers ",
order_seq: 1,
},
],
},
{
title: "Map",
url: "/map",
slug: "map",
order_seq: 3,
icon: "Map",
sub_items: [],
},
];
// Process the navigation data
const navigationItems = processNavigationItems(navItemDatas);
// Create nav items and their sub-items
for (const navItemData of navItemDatas) {
const { sub_items, ...navItemFields } = navItemData;
await prisma.navItems.create({
// Create all navigation items
for (const item of navigationItems) {
await prisma.navigationItem.create({
data: {
...navItemFields,
sub_items: {
create: sub_items,
},
title: item.title,
url: item.url,
slug: item.slug,
icon: item.icon,
path: item.path,
level: item.level,
isActive: item.isActive,
orderSeq: item.orderSeq,
},
});
}
console.log("Seed data created successfully", navItemDatas);
console.log(`Created ${navigationItems.length} navigation items`);
}
main()

View File

@ -6,13 +6,13 @@ export const navSubItemsSchema = z.object({
url: z.string(),
slug: z.string(),
icon: z.string(),
is_active: z.boolean().default(false),
order_seq: z.number().int(),
nav_item_id: z.string().uuid(),
created_at: z.date(),
updated_at: z.date(),
created_by: z.string().uuid().nullable(),
updated_by: z.string().uuid().nullable(),
isActive: z.boolean().default(false),
orderSeq: z.number().int(),
navItemId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: z.string().uuid().nullable(),
updatedBy: z.string().uuid().nullable(),
});
export type NavSubItems = z.infer<typeof navSubItemsSchema>;
@ -23,13 +23,13 @@ export const navItemsSchema = z.object({
url: z.string(),
slug: z.string(),
icon: z.string(),
is_active: z.boolean().default(false),
order_seq: z.number().int(),
created_at: z.date(),
updated_at: z.date(),
created_by: z.string().uuid().nullable(),
updated_by: z.string().uuid().nullable(),
sub_items: z.array(navSubItemsSchema),
isActive: z.boolean().default(false),
orderSeq: z.number().int(),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: z.string().uuid().nullable(),
updatedBy: z.string().uuid().nullable(),
subItems: z.array(navSubItemsSchema),
});
export type NavItems = z.infer<typeof navItemsSchema>;
@ -43,9 +43,9 @@ export const navItemsInsertSchema = navItemsSchema.pick({
url: true,
slug: true,
icon: true,
is_active: true,
order_seq: true,
sub_items: true,
isActive: true,
orderSeq: true,
subItems: true,
});
export type NavItemsInsert = z.infer<typeof navItemsInsertSchema>;
@ -55,9 +55,9 @@ export const navItemsUpdateSchema = navItemsSchema.pick({
url: true,
slug: true,
icon: true,
is_active: true,
order_seq: true,
sub_items: true,
isActive: true,
orderSeq: true,
subItems: true,
});
export type NavItemsUpdate = z.infer<typeof navItemsUpdateSchema>;

View File

@ -0,0 +1,62 @@
import { z } from "zod";
// Define the Zod schemas
export const navigationItemSchema = z.object({
id: z.string().uuid(),
title: z.string(),
url: z.string().nullable(),
slug: z.string(),
icon: z.string().nullable(),
path: z.string(),
level: z.number().int(),
isActive: z.boolean().default(false),
orderSeq: z.number().int(),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: z.string().uuid().nullable(),
updatedBy: z.string().uuid().nullable(),
});
export const createNavigationItemDTOSchema = z.object({
title: z.string(),
url: z.string(),
slug: z.string(),
icon: z.string(),
path: z.string(),
level: z.number().int(),
isActive: z.boolean().default(false),
orderSeq: z.number().int(),
createdBy: z.string().uuid().nullable(),
});
export const updateNavigationItemDTOSchema = z.object({
title: z.string().optional(),
url: z.string().optional(),
slug: z.string().optional(),
icon: z.string().optional(),
path: z.string().optional(),
level: z.number().int().optional(),
isActive: z.boolean().optional(),
orderSeq: z.number().int().optional(),
updatedBy: z.string().uuid().nullable().optional(),
});
export const NavigationItemResponseSchema = z.object({
success: z.boolean(),
message: z.string().optional(),
error: z.string().optional(),
errors: z.record(z.string()).optional(),
data: navigationItemSchema,
});
// Infer the types from the schemas
export type NavigationItem = z.infer<typeof navigationItemSchema>;
export type CreateNavigationItemDTO = z.infer<
typeof createNavigationItemDTOSchema
>;
export type UpdateNavigationItemDTO = z.infer<
typeof updateNavigationItemDTOSchema
>;
export type NavigationItemResponse = z.infer<
typeof NavigationItemResponseSchema
>;

View File

@ -0,0 +1,15 @@
import {
CreateNavigationItemDTO,
NavigationItem,
UpdateNavigationItemDTO,
} from "../entities/models/navigation-item.model";
export interface NavigationRepository {
findAll(): Promise<NavigationItem[]>;
findById(id: string): Promise<NavigationItem | null>;
findByPath(path: string): Promise<NavigationItem[]>;
create(data: CreateNavigationItemDTO): Promise<NavigationItem>;
update(id: string, data: UpdateNavigationItemDTO): Promise<NavigationItem>;
delete(id: string): Promise<void>;
reorder(items: { id: string; orderSeq: number }[]): Promise<void>;
}

View File

@ -0,0 +1,75 @@
// useCases/navigationUseCases.ts
import {
CreateNavigationItemDTO,
NavigationItem,
UpdateNavigationItemDTO,
} from "../entities/models/navigation-item.model";
import { NavigationRepository } from "../repositories/navigation-item.repository";
export class NavigationUseCases {
constructor(private repository: NavigationRepository) {}
async getNavigationTree(): Promise<NavigationItem[]> {
return this.repository.findAll();
}
async getChildren(parentPath: string): Promise<NavigationItem[]> {
return this.repository.findByPath(parentPath);
}
async createNavigationItem(
data: CreateNavigationItemDTO
): Promise<NavigationItem> {
// Calculate level based on path
const level = data.path.split(".").length - 1;
return this.repository.create({ ...data, level });
}
async updateNavigationItem(
id: string,
data: UpdateNavigationItemDTO
): Promise<NavigationItem> {
if (data.path) {
data.level = data.path.split(".").length - 1;
}
return this.repository.update(id, data);
}
async deleteNavigationItem(id: string): Promise<void> {
return this.repository.delete(id);
}
async reorderItems(items: { id: string; orderSeq: number }[]): Promise<void> {
return this.repository.reorder(items);
}
async moveItem(
id: string,
newParentPath: string,
newOrderSeq: number
): Promise<NavigationItem> {
const item = await this.repository.findById(id);
if (!item) throw new Error("Item not found");
const oldPath = item.path;
const newPath = newParentPath
? `${newParentPath}.${newOrderSeq}`
: `${newOrderSeq}`;
// Update all children paths
const children = await this.repository.findByPath(oldPath);
for (const child of children) {
const relativePath = child.path.slice(oldPath.length);
await this.repository.update(child.id, {
path: newPath + relativePath,
});
}
return this.repository.update(id, {
path: newPath,
orderSeq: newOrderSeq,
level: newPath.split(".").length - 1,
});
}
}

View File

@ -0,0 +1,184 @@
import { useState, useCallback, useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { NavigationUseCases } from "@/src/applications/usecases/navigation-item.usecase";
import { NavigationItem } from "@prisma/client";
import {
NavigationItemFormData,
navigationItemSchema,
} from "../validators/navigation-item.validator";
import {
CreateNavigationItemDTO,
UpdateNavigationItemDTO,
} from "@/src/applications/entities/models/navigation-item.model";
import { toast } from "@/hooks/use-toast";
export const useNavigationItem = (navigationUseCases: NavigationUseCases) => {
const [items, setItems] = useState<NavigationItem[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [formData, setFormData] = useState<NavigationItemFormData>({
title: "",
url: "",
slug: "",
icon: "",
isActive: false,
orderSeq: 0,
parentId: null,
});
const [errors, setErrors] = useState<
Partial<Record<keyof NavigationItemFormData, string>>
>({});
const form = useForm<NavigationItemFormData>({
resolver: zodResolver(navigationItemSchema),
});
const loadNavigationTree = useCallback(async () => {
try {
setLoading(true);
const data = await navigationUseCases.getNavigationTree();
setItems(data);
setError(null);
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to load navigation"
);
toast({
title: "Error",
description:
err instanceof Error ? err.message : "Failed to load navigation",
variant: "destructive",
});
} finally {
setLoading(false);
}
}, [navigationUseCases]);
const createItem = useCallback(
async (data: CreateNavigationItemDTO) => {
try {
setLoading(true);
await navigationUseCases.createNavigationItem(data);
await loadNavigationTree();
setError(null);
toast({
title: "Success",
description: "Navigation item created successfully",
});
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to create item");
toast({
title: "Error",
description:
err instanceof Error ? err.message : "Failed to create item",
variant: "destructive",
});
throw err;
} finally {
setLoading(false);
}
},
[navigationUseCases, loadNavigationTree]
);
const updateItem = useCallback(
async (id: string, data: UpdateNavigationItemDTO) => {
try {
setLoading(true);
await navigationUseCases.updateNavigationItem(id, data);
await loadNavigationTree();
setError(null);
toast({
title: "Success",
description: "Navigation item updated successfully",
});
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to update item");
toast({
title: "Error",
description:
err instanceof Error ? err.message : "Failed to update item",
variant: "destructive",
});
throw err;
} finally {
setLoading(false);
}
},
[navigationUseCases, loadNavigationTree]
);
const deleteItem = useCallback(
async (id: string) => {
try {
setLoading(true);
await navigationUseCases.deleteNavigationItem(id);
await loadNavigationTree();
setError(null);
toast({
title: "Success",
description: "Navigation item deleted successfully",
});
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to delete item");
toast({
title: "Error",
description:
err instanceof Error ? err.message : "Failed to delete item",
variant: "destructive",
});
throw err;
} finally {
setLoading(false);
}
},
[navigationUseCases, loadNavigationTree]
);
const moveItem = useCallback(
async (id: string, newParentPath: string, newOrderSeq: number) => {
try {
setLoading(true);
await navigationUseCases.moveItem(id, newParentPath, newOrderSeq);
await loadNavigationTree();
setError(null);
toast({
title: "Success",
description: "Navigation item moved successfully",
});
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to move item");
toast({
title: "Error",
description:
err instanceof Error ? err.message : "Failed to move item",
variant: "destructive",
});
throw err;
} finally {
setLoading(false);
}
},
[navigationUseCases, loadNavigationTree]
);
useEffect(() => {
loadNavigationTree();
}, [loadNavigationTree]);
return {
items,
loading,
error,
form,
formData,
errors,
setFormData,
createItem,
updateItem,
deleteItem,
moveItem,
refresh: loadNavigationTree,
};
};

View File

@ -11,12 +11,12 @@ import { NavItemsRepository } from "@/src/applications/repositories/nav-items.re
export class NavItemsRepositoryImpl implements NavItemsRepository {
async getNavItems(): Promise<NavItemsResponse> {
const data = await db.navItems.findMany({
const data = await db.navItem.findMany({
where: {
is_active: true,
isActive: true,
},
include: {
sub_items: true,
subItems: true,
},
});
return {
@ -41,7 +41,7 @@ export class NavItemsRepositoryImpl implements NavItemsRepository {
}
async deleteNavItems(id: string): Promise<NavItemsResponse> {
await db.navItems.delete({
await db.navItem.delete({
where: {
id,
},

View File

@ -0,0 +1,75 @@
// repositories/navigationRepository.ts
import db from "@/lib/db";
import {
CreateNavigationItemDTO,
NavigationItem,
UpdateNavigationItemDTO,
} from "@/src/applications/entities/models/navigation-item.model";
import { NavigationRepository } from "@/src/applications/repositories/navigation-item.repository";
export class NavigationItemRepository implements NavigationRepository {
async findAll(): Promise<NavigationItem[]> {
return db.navigationItem.findMany({
orderBy: [{ path: "asc" }, { orderSeq: "asc" }],
});
}
async findById(id: string): Promise<NavigationItem | null> {
return db.navigationItem.findUnique({
where: { id },
});
}
async findByPath(path: string): Promise<NavigationItem[]> {
return db.navigationItem.findMany({
where: {
path: { startsWith: path },
},
orderBy: [{ path: "asc" }, { orderSeq: "asc" }],
});
}
async create(data: CreateNavigationItemDTO): Promise<NavigationItem> {
return db.navigationItem.create({
data,
});
}
async update(
id: string,
data: UpdateNavigationItemDTO
): Promise<NavigationItem> {
return db.navigationItem.update({
where: { id },
data,
});
}
async delete(id: string): Promise<void> {
// Get the item to be deleted
const item = await db.navigationItem.findUnique({
where: { id },
});
if (!item) return;
// Delete all items with paths that start with itdbem's path
await db.navigationItem.deleteMany({
where: {
path: { startsWith: item.path },
},
});
}
async reorder(items: { id: string; orderSeq: number }[]): Promise<void> {
await db.$transaction(
items.map((item) =>
db.navigationItem.update({
where: { id: item.id },
data: { orderSeq: item.orderSeq },
})
)
);
}
}

View File

@ -0,0 +1,39 @@
// validators/navigationValidator.ts
import { z } from "zod";
export const navigationItemSchema = z.object({
title: z
.string()
.min(1, "Title is required")
.max(255, "Title must be less than 255 characters"),
url: z
.string()
.min(1, "URL is required")
.max(255, "URL must be less than 255 characters")
.regex(/^\//, "URL must start with /")
.regex(
/^[a-zA-Z0-9\-/_]+$/,
"URL can only contain letters, numbers, hyphens, and forward slashes"
),
slug: z
.string()
.min(1, "Slug is required")
.max(255, "Slug must be less than 255 characters")
.regex(
/^[a-z0-9\-]+$/,
"Slug can only contain lowercase letters, numbers, and hyphens"
),
icon: z
.string()
.min(1, "Icon is required")
.max(100, "Icon name must be less than 100 characters"),
isActive: z.boolean(),
orderSeq: z
.number()
.int("Order must be an integer")
.min(1, "Order must be greater than 0"),
parentId: z.string().uuid().optional().nullable(),
});
export type NavigationItemFormData = z.infer<typeof navigationItemSchema>;