add shema for map fitur
This commit is contained in:
parent
a0b0289a86
commit
538a8f4fa8
|
@ -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]);
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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
|
||||
>;
|
|
@ -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>;
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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 },
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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>;
|
Loading…
Reference in New Issue