fecth dynamic navitem
This commit is contained in:
parent
7c58a7e46c
commit
bc0c6438a2
|
@ -0,0 +1,211 @@
|
|||
"use server";
|
||||
|
||||
import {
|
||||
NavItems,
|
||||
navItemsDeleteSchema,
|
||||
NavItemsInsert,
|
||||
navItemsInsertSchema,
|
||||
navItemsUpdateSchema,
|
||||
} from "@/src/applications/entities/models/nav-items.model";
|
||||
import { NavItemsRepository } from "@/src/applications/repositories/nav-items.repository";
|
||||
import { NavItemsRepositoryImpl } from "@/src/infrastructure/repositories/nav-items.repository.impl";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
|
||||
// Initialize repository
|
||||
const navItemsRepo: NavItemsRepository = new NavItemsRepositoryImpl();
|
||||
|
||||
/**
|
||||
* Get all navigation items
|
||||
*/
|
||||
export async function getNavItems() {
|
||||
return await navItemsRepo.getNavItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new navigation item
|
||||
*/
|
||||
export async function createNavItem(
|
||||
formData: FormData | Record<string, NavItems>
|
||||
) {
|
||||
try {
|
||||
const data =
|
||||
formData instanceof FormData
|
||||
? Object.fromEntries(formData.entries())
|
||||
: formData;
|
||||
|
||||
// Parse and validate input data
|
||||
const validatedData = navItemsInsertSchema.parse(data);
|
||||
|
||||
// Call repository method (assuming it exists)
|
||||
const result = await navItemsRepo.createNavItems(validatedData);
|
||||
|
||||
// Revalidate cache if successful
|
||||
if (result.success) {
|
||||
revalidatePath("/admin/navigation");
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
errors: error.errors.reduce(
|
||||
(acc, curr) => {
|
||||
acc[curr.path.join(".")] = curr.message;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
),
|
||||
message: "Validation failed",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to create navigation item",
|
||||
message:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing navigation item
|
||||
*/
|
||||
export async function updateNavItem(
|
||||
id: string,
|
||||
formData: FormData | Record<string, unknown>
|
||||
) {
|
||||
try {
|
||||
const data =
|
||||
formData instanceof FormData
|
||||
? Object.fromEntries(formData.entries())
|
||||
: formData;
|
||||
|
||||
// Parse and validate input data
|
||||
const validatedData = navItemsUpdateSchema.parse(data);
|
||||
|
||||
// Call repository method (assuming it exists)
|
||||
const result = await navItemsRepo.updateNavItems(id, validatedData);
|
||||
|
||||
// Revalidate cache if successful
|
||||
if (result.success) {
|
||||
revalidatePath("/admin/navigation");
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
errors: error.errors.reduce(
|
||||
(acc, curr) => {
|
||||
acc[curr.path.join(".")] = curr.message;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
),
|
||||
message: "Validation failed",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to update navigation item",
|
||||
message:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a navigation item
|
||||
*/
|
||||
export async function deleteNavItem(id: string) {
|
||||
try {
|
||||
// Parse and validate input
|
||||
const validatedData = navItemsDeleteSchema.parse({ id });
|
||||
|
||||
// Call repository method (assuming it exists)
|
||||
const result = await navItemsRepo.deleteNavItems(validatedData.id);
|
||||
|
||||
// Revalidate cache if successful
|
||||
if (result.success) {
|
||||
revalidatePath("/admin/navigation");
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
errors: error.errors.reduce(
|
||||
(acc, curr) => {
|
||||
acc[curr.path.join(".")] = curr.message;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
),
|
||||
message: "Validation failed",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to delete navigation item",
|
||||
message:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Toggle active status of a navigation item
|
||||
// */
|
||||
// export async function toggleNavItemActive(id: string) {
|
||||
// try {
|
||||
// // Call repository method (assuming it exists)
|
||||
// const result = await navItemsRepo.toggleActive(id);
|
||||
|
||||
// // Revalidate cache if successful
|
||||
// if (result.success) {
|
||||
// revalidatePath("/admin/navigation");
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// } catch (error) {
|
||||
// return {
|
||||
// success: false,
|
||||
// error: "Failed to toggle navigation item status",
|
||||
// message:
|
||||
// error instanceof Error ? error.message : "Unknown error occurred",
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Update navigation items order
|
||||
// */
|
||||
// export async function updateNavItemsOrder(
|
||||
// items: { id: string; order_seq: number }[]
|
||||
// ) {
|
||||
// try {
|
||||
// // Call repository method (assuming it exists)
|
||||
// const result = await navItemsRepo.updateOrder(items);
|
||||
|
||||
// // Revalidate cache if successful
|
||||
// if (result.success) {
|
||||
// revalidatePath("/admin/navigation");
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// } catch (error) {
|
||||
// return {
|
||||
// success: false,
|
||||
// error: "Failed to update navigation order",
|
||||
// message:
|
||||
// error instanceof Error ? error.message : "Unknown error occurred",
|
||||
// };
|
||||
// }
|
||||
// }
|
|
@ -16,9 +16,7 @@ import {
|
|||
|
||||
export default function Page() {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<>
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
|
@ -46,7 +44,6 @@ export default function Page() {
|
|||
</div>
|
||||
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { checkSession } from "@/actions/auth/session";
|
||||
import { AppSidebar } from "@/components/app-sidebar";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
|
@ -1,33 +1,36 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
AudioWaveform,
|
||||
BookOpen,
|
||||
Bot,
|
||||
Command,
|
||||
Frame,
|
||||
GalleryVerticalEnd,
|
||||
Map,
|
||||
PieChart,
|
||||
Settings2,
|
||||
Loader2,
|
||||
SquareTerminal,
|
||||
} from "lucide-react"
|
||||
} from "lucide-react";
|
||||
|
||||
import { NavMain } from "@/components/nav-main"
|
||||
import { NavProjects } from "@/components/nav-projects"
|
||||
import { NavUser } from "@/components/nav-user"
|
||||
import { TeamSwitcher } from "@/components/team-switcher"
|
||||
import { NavMain } from "@/components/nav-main";
|
||||
import { NavProjects } from "@/components/nav-projects";
|
||||
import { NavUser } from "@/components/nav-user";
|
||||
import { TeamSwitcher } from "@/components/team-switcher";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarRail,
|
||||
} from "@/components/ui/sidebar"
|
||||
} from "@/components/ui/sidebar";
|
||||
import { NavItemsGet } from "@/src/applications/entities/models/nav-items.model";
|
||||
import { getNavItems } from "@/actions/dashboard/nav-items";
|
||||
import DynamicIcon, { DynamicIconProps } from "./dynamic-icon";
|
||||
|
||||
// This is sample data.
|
||||
const data = {
|
||||
// 🔧 Konstanta untuk localStorage key
|
||||
const NAV_ITEMS_STORAGE_KEY = "navItemsData";
|
||||
|
||||
// 🎨 Data fallback
|
||||
const fallbackData = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
|
@ -71,71 +74,7 @@ const data = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Models",
|
||||
url: "#",
|
||||
icon: Bot,
|
||||
items: [
|
||||
{
|
||||
title: "Genesis",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Explorer",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Quantum",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
url: "#",
|
||||
icon: BookOpen,
|
||||
items: [
|
||||
{
|
||||
title: "Introduction",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Get Started",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Tutorials",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Changelog",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: Settings2,
|
||||
items: [
|
||||
{
|
||||
title: "General",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Billing",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Limits",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
// additional items...
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
|
@ -143,33 +82,109 @@ const data = {
|
|||
url: "#",
|
||||
icon: Frame,
|
||||
},
|
||||
{
|
||||
name: "Sales & Marketing",
|
||||
url: "#",
|
||||
icon: PieChart,
|
||||
},
|
||||
{
|
||||
name: "Travel",
|
||||
url: "#",
|
||||
icon: Map,
|
||||
},
|
||||
// additional projects...
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
const [navItems, setNavItems] = useState<NavItemsGet | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 📦 Ambil data dari localStorage
|
||||
const loadNavItemsFromStorage = (): NavItemsGet | null => {
|
||||
const storedData = localStorage.getItem(NAV_ITEMS_STORAGE_KEY);
|
||||
return storedData ? (JSON.parse(storedData) as NavItemsGet) : null;
|
||||
};
|
||||
|
||||
// ⚡ Fungsi untuk membandingkan dua objek secara deep
|
||||
const isDataEqual = (data1: any, data2: any): boolean =>
|
||||
JSON.stringify(data1) === JSON.stringify(data2);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAndSyncNavItems = async () => {
|
||||
try {
|
||||
const localData = loadNavItemsFromStorage();
|
||||
|
||||
if (localData) {
|
||||
setNavItems(localData);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
const response = await getNavItems();
|
||||
if (response.success && response.data) {
|
||||
const fetchedData = response.data as NavItemsGet;
|
||||
const currentStoredData = loadNavItemsFromStorage();
|
||||
|
||||
// 🔄 Jika data berbeda, update localStorage dan state
|
||||
if (!isDataEqual(currentStoredData, fetchedData)) {
|
||||
localStorage.setItem(
|
||||
NAV_ITEMS_STORAGE_KEY,
|
||||
JSON.stringify(fetchedData)
|
||||
);
|
||||
setNavItems(fetchedData);
|
||||
}
|
||||
} else {
|
||||
setError(response.error || "Failed to load navigation items");
|
||||
}
|
||||
} catch (err) {
|
||||
setError("An unexpected error occurred while fetching navigation");
|
||||
console.error(err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAndSyncNavItems();
|
||||
}, []);
|
||||
|
||||
// 🎛️ Transformasi data DB ke format NavMain
|
||||
const formatNavItems = React.useMemo(() => {
|
||||
if (!navItems) return fallbackData.navMain;
|
||||
return navItems.map((item) => ({
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
icon: item.icon,
|
||||
isActive: item.is_active,
|
||||
items: item.sub_items.map((subItem) => ({
|
||||
title: subItem.title,
|
||||
url: subItem.url,
|
||||
isActive: subItem.is_active,
|
||||
})),
|
||||
}));
|
||||
}, [navItems]);
|
||||
|
||||
// 🧩 Komponen NavMain dinamis dengan ikon
|
||||
const DynamicNavMain = ({ items }: { items: any[] }) => (
|
||||
<NavMain
|
||||
items={items.map((item) => ({
|
||||
...item,
|
||||
icon: () => <DynamicIcon iconName={item.icon} size={24} />,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
<SidebarHeader>
|
||||
<TeamSwitcher teams={data.teams} />
|
||||
<TeamSwitcher teams={fallbackData.teams} />
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavProjects projects={data.projects} />
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="px-4 py-2 text-sm text-destructive">{error}</div>
|
||||
) : (
|
||||
<DynamicNavMain items={formatNavItems} />
|
||||
)}
|
||||
<NavProjects projects={fallbackData.projects} />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={data.user} />
|
||||
<NavUser user={fallbackData.user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
// components/icons/DynamicIcon.tsx
|
||||
import React from "react";
|
||||
import * as TablerIcons from "@tabler/icons-react";
|
||||
|
||||
export interface DynamicIconProps {
|
||||
iconName: string;
|
||||
size?: number;
|
||||
color?: string;
|
||||
className?: string;
|
||||
stroke?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 DynamicIcon - Reusable component untuk merender ikon @tabler/icons-react secara dinamis
|
||||
* @param iconName - Nama ikon (harus sesuai dengan nama ikon dari @tabler/icons-react, contoh: "IconUsers")
|
||||
* @param size - Ukuran ikon (default: 24)
|
||||
* @param color - Warna ikon (opsional)
|
||||
* @param className - CSS class tambahan (opsional)
|
||||
* @param stroke - Ketebalan garis (opsional, default dari library)
|
||||
*/
|
||||
const DynamicIcon: React.FC<DynamicIconProps> = ({
|
||||
iconName,
|
||||
size = 32,
|
||||
color,
|
||||
className = "IconAlertHexagon",
|
||||
stroke,
|
||||
}) => {
|
||||
// Safety check: Ensure the iconName exists in TablerIcons
|
||||
if (!(iconName in TablerIcons)) {
|
||||
console.warn(`Icon "${iconName}" not found in @tabler/icons-react library`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const IconComponent = TablerIcons[
|
||||
iconName as keyof typeof TablerIcons
|
||||
] as React.ComponentType<{
|
||||
size?: number;
|
||||
color?: string;
|
||||
className?: string;
|
||||
stroke?: number;
|
||||
}>;
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
size={size}
|
||||
color={color}
|
||||
className={className}
|
||||
stroke={stroke}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicIcon;
|
|
@ -8,8 +8,8 @@ declare const globalThis: {
|
|||
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
|
||||
} & typeof global;
|
||||
|
||||
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();
|
||||
const db = globalThis.prismaGlobal ?? prismaClientSingleton();
|
||||
|
||||
export default prisma;
|
||||
export default db;
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;
|
||||
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = db;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"@react-email/components": "0.0.33",
|
||||
"@supabase/ssr": "latest",
|
||||
"@supabase/supabase-js": "latest",
|
||||
"@tabler/icons-react": "^3.30.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
@ -46,6 +47,7 @@
|
|||
"@types/react-dom": "19.0.2",
|
||||
"postcss": "8.4.49",
|
||||
"prisma": "^6.3.1",
|
||||
"prisma-json-types-generator": "^3.2.2",
|
||||
"react-email": "3.0.7",
|
||||
"supabase": "^2.12.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
|
@ -1590,6 +1592,23 @@
|
|||
"@prisma/get-platform": "6.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/generator-helper": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-6.0.0.tgz",
|
||||
"integrity": "sha512-5DkG7hspZo6U4OtqI2W0JcgtY37sr7HgT8Q0W/sjL4VoV4px6ivzK6Eif5bKM7q+S4yFUHtjUt/3s69ErfLn7A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/generator-helper/node_modules/@prisma/debug": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.0.0.tgz",
|
||||
"integrity": "sha512-eUjoNThlDXdyJ1iQ2d7U6aTVwm59EwvODb5zFVNJEokNoSiQmiYWNzZIwZyDmZ+j51j42/0iTaHIJ4/aZPKFRg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.3.1.tgz",
|
||||
|
@ -2901,6 +2920,32 @@
|
|||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tabler/icons": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.30.0.tgz",
|
||||
"integrity": "sha512-c8OKLM48l00u9TFbh2qhSODMONIzML8ajtCyq95rW8vzkWcBrKRPM61tdkThz2j4kd5u17srPGIjqdeRUZdfdw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/codecalm"
|
||||
}
|
||||
},
|
||||
"node_modules/@tabler/icons-react": {
|
||||
"version": "3.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.30.0.tgz",
|
||||
"integrity": "sha512-9KZ9D1UNAyjlLkkYp2HBPHdf6lAJ2aelDqh8YYAnnmLF3xwprWKxxW8+zw5jlI0IwdfN4XFFuzqePkaw+DpIOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tabler/icons": "3.30.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/codecalm"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
|
@ -5334,6 +5379,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prisma-json-types-generator": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/prisma-json-types-generator/-/prisma-json-types-generator-3.2.2.tgz",
|
||||
"integrity": "sha512-kvEbJPIP5gxk65KmLs0nAvY+CxpqVMWb4OsEvXlyXZmp2IGfi5f52BUV7ezTYQNjRPZyR4QlayWJXffoqVVAfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@prisma/generator-helper": "6.0.0",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"prisma-json-types-generator": "index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/arthurfiorette/prisma-json-types-generator?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prisma": "^5 || ^6",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
"start": "next start",
|
||||
"db:seed": "npx prisma db seed"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node prisma/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^4.0.0",
|
||||
|
@ -23,6 +27,7 @@
|
|||
"@react-email/components": "0.0.33",
|
||||
"@supabase/ssr": "latest",
|
||||
"@supabase/supabase-js": "latest",
|
||||
"@tabler/icons-react": "^3.30.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
@ -43,6 +48,7 @@
|
|||
"@types/react-dom": "19.0.2",
|
||||
"postcss": "8.4.49",
|
||||
"prisma": "^6.3.1",
|
||||
"prisma-json-types-generator": "^3.2.2",
|
||||
"react-email": "3.0.7",
|
||||
"supabase": "^2.12.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `createdAt` on the `contact_messages` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `updatedAt` on the `contact_messages` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `birthDate` on the `profiles` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `userId` on the `profiles` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `createdAt` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `emailVerified` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `firstName` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `lastName` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `lastSignedIn` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `updatedAt` on the `users` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[user_id]` on the table `profiles` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `user_id` to the `profiles` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `updated_at` to the `users` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- CreateExtension
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "profiles" DROP CONSTRAINT "profiles_userId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "profiles_userId_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" DROP COLUMN "createdAt",
|
||||
DROP COLUMN "updatedAt",
|
||||
ADD COLUMN "created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
ADD COLUMN "updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "profiles" DROP COLUMN "birthDate",
|
||||
DROP COLUMN "userId",
|
||||
ADD COLUMN "birth_date" TIMESTAMP(3),
|
||||
ADD COLUMN "user_id" TEXT NOT NULL,
|
||||
ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP COLUMN "createdAt",
|
||||
DROP COLUMN "emailVerified",
|
||||
DROP COLUMN "firstName",
|
||||
DROP COLUMN "lastName",
|
||||
DROP COLUMN "lastSignedIn",
|
||||
DROP COLUMN "updatedAt",
|
||||
ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "email_verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "first_name" TEXT,
|
||||
ADD COLUMN "last_name" TEXT,
|
||||
ADD COLUMN "last_signed_in" TIMESTAMP(3),
|
||||
ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "nav_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"url" VARCHAR(255) NOT NULL,
|
||||
"icon" VARCHAR(100) NOT NULL,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT false,
|
||||
"order_seq" INTEGER NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"created_by" UUID,
|
||||
"updated_by" UUID,
|
||||
|
||||
CONSTRAINT "nav_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sub_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"url" VARCHAR(255) NOT NULL,
|
||||
"order_seq" INTEGER NOT NULL,
|
||||
"nav_item_id" UUID NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"created_by" UUID,
|
||||
"updated_by" UUID,
|
||||
|
||||
CONSTRAINT "sub_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_items_title_idx" ON "nav_items"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_items_is_active_idx" ON "nav_items"("is_active");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sub_items_nav_item_id_idx" ON "sub_items"("nav_item_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sub_items_title_idx" ON "sub_items"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "profiles_user_id_key" ON "profiles"("user_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sub_items" ADD CONSTRAINT "sub_items_nav_item_id_fkey" FOREIGN KEY ("nav_item_id") REFERENCES "nav_items"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `contact_messages` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- The `id` column on the `contact_messages` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
- You are about to alter the column `name` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `email` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `phone` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(20)`.
|
||||
- You are about to alter the column `message_type` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(50)`.
|
||||
- You are about to alter the column `message_type_label` on the `contact_messages` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(50)`.
|
||||
- The primary key for the `profiles` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- The `id` column on the `profiles` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
- You are about to alter the column `phone` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(20)`.
|
||||
- You are about to alter the column `address` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `city` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`.
|
||||
- You are about to alter the column `country` on the `profiles` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`.
|
||||
- The primary key for the `users` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- You are about to alter the column `email` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `password` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `avatar` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `first_name` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to alter the column `last_name` on the `users` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||
- You are about to drop the `sub_items` table. If the table is not empty, all the data it contains will be lost.
|
||||
- Changed the type of `user_id` on the `profiles` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
- Changed the type of `id` on the `users` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "profiles" DROP CONSTRAINT "profiles_user_id_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "sub_items" DROP CONSTRAINT "sub_items_nav_item_id_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "contact_messages" DROP CONSTRAINT "contact_messages_pkey",
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
ALTER COLUMN "name" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "email" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "phone" SET DATA TYPE VARCHAR(20),
|
||||
ALTER COLUMN "message_type" SET DATA TYPE VARCHAR(50),
|
||||
ALTER COLUMN "message_type_label" SET DATA TYPE VARCHAR(50),
|
||||
ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now(),
|
||||
ADD CONSTRAINT "contact_messages_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "nav_items" ALTER COLUMN "created_at" SET DEFAULT now(),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT now();
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "profiles" DROP CONSTRAINT "profiles_pkey",
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
ALTER COLUMN "phone" SET DATA TYPE VARCHAR(20),
|
||||
ALTER COLUMN "address" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "city" SET DATA TYPE VARCHAR(100),
|
||||
ALTER COLUMN "country" SET DATA TYPE VARCHAR(100),
|
||||
DROP COLUMN "user_id",
|
||||
ADD COLUMN "user_id" UUID NOT NULL,
|
||||
ADD CONSTRAINT "profiles_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP CONSTRAINT "users_pkey",
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" UUID NOT NULL,
|
||||
ALTER COLUMN "email" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "password" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "avatar" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "first_name" SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN "last_name" SET DATA TYPE VARCHAR(255),
|
||||
ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "sub_items";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "nav_sub_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"url" VARCHAR(255) NOT NULL,
|
||||
"order_seq" INTEGER NOT NULL,
|
||||
"nav_item_id" UUID NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT now(),
|
||||
"created_by" UUID,
|
||||
"updated_by" UUID,
|
||||
|
||||
CONSTRAINT "nav_sub_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_sub_items_nav_item_id_idx" ON "nav_sub_items"("nav_item_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "nav_sub_items_title_idx" ON "nav_sub_items"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "profiles_user_id_key" ON "profiles"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "users_role_idx" ON "users"("role");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "nav_sub_items" ADD CONSTRAINT "nav_sub_items_nav_item_id_fkey" FOREIGN KEY ("nav_item_id") REFERENCES "nav_items"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -6,63 +6,105 @@
|
|||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["postgresqlExtensions"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
directUrl = env("DIRECT_URL")
|
||||
extensions = [pgcrypto]
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id
|
||||
email String @unique
|
||||
emailVerified Boolean @default(false)
|
||||
password String?
|
||||
firstName String?
|
||||
lastName String?
|
||||
avatar String?
|
||||
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)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
lastSignedIn DateTime?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
last_signed_in DateTime?
|
||||
metadata Json?
|
||||
|
||||
// Relations (optional examples)
|
||||
profile Profile?
|
||||
|
||||
@@map("users") // Maps to Supabase's 'users' table
|
||||
@@index([role])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Profile {
|
||||
id String @id @default(uuid())
|
||||
userId String @unique
|
||||
bio String?
|
||||
phone String?
|
||||
address String?
|
||||
city String?
|
||||
country String?
|
||||
birthDate DateTime?
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
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])
|
||||
|
||||
@@index([user_id])
|
||||
@@map("profiles") // Maps to Supabase's 'profiles' table
|
||||
}
|
||||
|
||||
model ContactMessages {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()"))
|
||||
name String?
|
||||
email String?
|
||||
phone String?
|
||||
message_type String?
|
||||
message_type_label String?
|
||||
message String?
|
||||
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)
|
||||
createdAt DateTime @default(dbgenerated("now()")) @db.Timestamptz(6)
|
||||
updatedAt DateTime @default(dbgenerated("now()")) @updatedAt @db.Timestamptz(6)
|
||||
created_at DateTime @default(dbgenerated("now()")) @db.Timestamptz(6)
|
||||
updated_at DateTime @default(dbgenerated("now()")) @updatedAt @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
|
||||
|
||||
@@index([title])
|
||||
@@index([is_active])
|
||||
@@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)
|
||||
|
||||
@@index([nav_item_id])
|
||||
@@index([title])
|
||||
@@map("nav_sub_items")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
admin
|
||||
staff
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
const { PrismaClient } = require("@prisma/client");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
// Clear existing data
|
||||
await prisma.navSubItems.deleteMany({});
|
||||
await prisma.navItems.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: [],
|
||||
},
|
||||
];
|
||||
|
||||
// Create nav items and their sub-items
|
||||
for (const navItemData of navItemDatas) {
|
||||
const { sub_items, ...navItemFields } = navItemData;
|
||||
|
||||
await prisma.navItems.create({
|
||||
data: {
|
||||
...navItemFields,
|
||||
sub_items: {
|
||||
create: sub_items,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Seed data created successfully", navItemDatas);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const navSubItemsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
title: z.string(),
|
||||
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(),
|
||||
});
|
||||
|
||||
export type NavSubItems = z.infer<typeof navSubItemsSchema>;
|
||||
|
||||
export const navItemsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
title: z.string(),
|
||||
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),
|
||||
});
|
||||
|
||||
export type NavItems = z.infer<typeof navItemsSchema>;
|
||||
|
||||
export const navItemsGetSchema = z.array(navItemsSchema);
|
||||
|
||||
export type NavItemsGet = z.infer<typeof navItemsGetSchema>;
|
||||
|
||||
export const navItemsInsertSchema = navItemsSchema.pick({
|
||||
title: true,
|
||||
url: true,
|
||||
slug: true,
|
||||
icon: true,
|
||||
is_active: true,
|
||||
order_seq: true,
|
||||
sub_items: true,
|
||||
});
|
||||
|
||||
export type NavItemsInsert = z.infer<typeof navItemsInsertSchema>;
|
||||
|
||||
export const navItemsUpdateSchema = navItemsSchema.pick({
|
||||
title: true,
|
||||
url: true,
|
||||
slug: true,
|
||||
icon: true,
|
||||
is_active: true,
|
||||
order_seq: true,
|
||||
sub_items: true,
|
||||
});
|
||||
|
||||
export type NavItemsUpdate = z.infer<typeof navItemsUpdateSchema>;
|
||||
|
||||
export const navItemsDeleteSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
});
|
||||
|
||||
export type NavItemsDelete = z.infer<typeof navItemsDeleteSchema>;
|
||||
|
||||
export interface NavItemsResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
errors?: Record<string, string>;
|
||||
data?: NavItems | NavItemsGet;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
NavItemsDelete,
|
||||
NavItemsGet,
|
||||
NavItemsInsert,
|
||||
NavItemsResponse,
|
||||
NavItemsUpdate,
|
||||
} from "../entities/models/nav-items.model";
|
||||
|
||||
export interface NavItemsRepository {
|
||||
getNavItems(): Promise<NavItemsResponse>;
|
||||
createNavItems(navItems: NavItemsInsert): Promise<NavItemsResponse>;
|
||||
updateNavItems(
|
||||
id: string,
|
||||
navItems: NavItemsUpdate
|
||||
): Promise<NavItemsResponse>;
|
||||
deleteNavItems(id: string): Promise<NavItemsResponse>;
|
||||
}
|
|
@ -2,8 +2,8 @@ import {
|
|||
ContactUs,
|
||||
ContactUsInsert,
|
||||
ContactUsResponse,
|
||||
} from "../../entities/models/contact-us.model";
|
||||
import { ContactUsRepository } from "../../repositories/contact-us.repository";
|
||||
} from "../entities/models/contact-us.model";
|
||||
import { ContactUsRepository } from "../repositories/contact-us.repository";
|
||||
|
||||
export class CreateContactUseCase {
|
||||
constructor(private contactRepository: ContactUsRepository) {}
|
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
NavItemsDelete,
|
||||
NavItemsGet,
|
||||
NavItemsInsert,
|
||||
NavItemsResponse,
|
||||
NavItemsUpdate,
|
||||
} from "../entities/models/nav-items.model";
|
||||
import { NavItemsRepository } from "../repositories/nav-items.repository";
|
||||
|
||||
export class NavItemsUseCase {
|
||||
constructor(private navItemsRepository: NavItemsRepository) {}
|
||||
|
||||
async executeGetNavItems(): Promise<NavItemsResponse> {
|
||||
return this.navItemsRepository.getNavItems();
|
||||
}
|
||||
|
||||
async executeCreateNavItems(
|
||||
navItems: NavItemsInsert
|
||||
): Promise<NavItemsResponse> {
|
||||
return this.navItemsRepository.createNavItems(navItems);
|
||||
}
|
||||
|
||||
async executeUpdateNavItems(
|
||||
id: string,
|
||||
navItems: NavItemsUpdate
|
||||
): Promise<NavItemsResponse> {
|
||||
return this.navItemsRepository.updateNavItems(id, navItems);
|
||||
}
|
||||
|
||||
async executeDeleteNavItems(id: string): Promise<NavItemsResponse> {
|
||||
return this.navItemsRepository.deleteNavItems(id);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { SignInResponse, SignInWithOtp } from "../../entities/models/user.model";
|
||||
import { SignInRepository } from "../../repositories/signin.repository";
|
||||
import { SignInResponse, SignInWithOtp } from "../entities/models/user.model";
|
||||
import { SignInRepository } from "../repositories/signin.repository";
|
||||
|
||||
export class SignInUseCase {
|
||||
constructor(private signInRepository: SignInRepository) {}
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import db from "@/lib/db";
|
||||
import {
|
||||
NavItemsDelete,
|
||||
NavItemsGet,
|
||||
NavItemsInsert,
|
||||
NavItemsResponse,
|
||||
navItemsSchema,
|
||||
NavItemsUpdate,
|
||||
} from "@/src/applications/entities/models/nav-items.model";
|
||||
import { NavItemsRepository } from "@/src/applications/repositories/nav-items.repository";
|
||||
|
||||
export class NavItemsRepositoryImpl implements NavItemsRepository {
|
||||
async getNavItems(): Promise<NavItemsResponse> {
|
||||
const data = await db.navItems.findMany({
|
||||
where: {
|
||||
is_active: true,
|
||||
},
|
||||
include: {
|
||||
sub_items: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
async createNavItems(navItems: NavItemsInsert): Promise<NavItemsResponse> {
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async updateNavItems(
|
||||
id: string,
|
||||
navItems: NavItemsUpdate
|
||||
): Promise<NavItemsResponse> {
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteNavItems(id: string): Promise<NavItemsResponse> {
|
||||
await db.navItems.delete({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Navigation item deleted",
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue