Admin part is added and navbar context file is added
This commit is contained in:
19
app/dashboard/admin/dynamicDashboard/page.tsx
Normal file
19
app/dashboard/admin/dynamicDashboard/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import DynamicDashboardPortal from "@/components/dashboard/admin/Configuration/dynamicDashboard";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function DynamicDashboardPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<DynamicDashboardPortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
20
app/dashboard/admin/language/page.tsx
Normal file
20
app/dashboard/admin/language/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import LanguagePortal from "@/components/dashboard/admin/Configuration/language";
|
||||||
|
import ModulesPortal from "@/components/dashboard/admin/Configuration/modules";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function LanguagePage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<LanguagePortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
21
app/dashboard/admin/mobileFlow/page.tsx
Normal file
21
app/dashboard/admin/mobileFlow/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
|
||||||
|
import MobileFlowsPortal from "@/components/dashboard/admin/Configuration/mobileFlows";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function MobileFlowPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<MobileFlowsPortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
19
app/dashboard/admin/modules/page.tsx
Normal file
19
app/dashboard/admin/modules/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import ModulesPortal from "@/components/dashboard/admin/Configuration/modules";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function ModulesPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<ModulesPortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
18
app/dashboard/admin/overview/page.tsx
Normal file
18
app/dashboard/admin/overview/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import AdminOverviewPortal from "@/components/dashboard/admin/overview";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function OverviewPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<AdminOverviewPortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
23
app/dashboard/admin/security/page.tsx
Normal file
23
app/dashboard/admin/security/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
|
||||||
|
import SecurityPortal from "@/components/dashboard/admin/Platform/security";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function SecurityPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<SecurityPortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
20
app/dashboard/admin/site/page.tsx
Normal file
20
app/dashboard/admin/site/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
|
||||||
|
import SitePortal from "@/components/dashboard/admin/site";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function AuditSurveyPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<SitePortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
19
app/dashboard/admin/tenants/page.tsx
Normal file
19
app/dashboard/admin/tenants/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
|
||||||
|
import TenantsPortal from "@/components/dashboard/admin/tenants";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function TenantsPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<TenantsPortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
20
app/dashboard/admin/users/page.tsx
Normal file
20
app/dashboard/admin/users/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import UserPortal from "@/components/dashboard/admin/users";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function UsersPage() {
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<div className="p-8 mx-3">
|
||||||
|
<UserPortal/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
"use client"// app/dashboard/layout.tsx
|
"use client";
|
||||||
|
|
||||||
import Sidebar from "@/components/layouts/sidebar";
|
import Sidebar from "@/components/layouts/sidebar";
|
||||||
import Navbar from "@/components/layouts/navbar";
|
import Navbar from "@/components/layouts/navbar";
|
||||||
import { Sparkles } from "lucide-react";
|
import { Sparkles } from "lucide-react";
|
||||||
|
import { NavProvider } from "@/components/layouts/context/navContext";
|
||||||
|
|
||||||
export default function DashboardLayout({
|
export default function DashboardLayout({
|
||||||
children,
|
children,
|
||||||
@@ -9,27 +11,29 @@ export default function DashboardLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className=" bg-[#071321] text-white ">
|
<NavProvider>
|
||||||
{/* Shared Sidebar */}
|
|
||||||
<Sidebar />
|
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col min-h-screen">
|
{/* Shared Sidebar - Now inside NavProvider */}
|
||||||
{/*Common Navbar for every webpage */}
|
<Sidebar />
|
||||||
<Navbar />
|
|
||||||
|
|
||||||
|
<div className="pl-64 flex flex-col ">
|
||||||
|
{/* Shared Navbar - Now inside NavProvider */}
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
<main className="flex-1 pl-68 bg-[#071321]">
|
{/* Main Page Content */}
|
||||||
{children}
|
<main className="flex-1 bg-[#071321]">
|
||||||
</main>
|
{children}
|
||||||
</div>
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Ask AI Floating Button common */}
|
{/* Ask AI Floating Button */}
|
||||||
<button className="fixed bottom-4 right-4 z-40 flex items-center gap-2
|
<button className="fixed bottom-4 right-4 z-40 flex items-center gap-2
|
||||||
rounded-full bg-[#17d4bd] hover:bg-[#13b19e] px-4 py-2.5 text-xs
|
rounded-full bg-[#17d4bd] hover:bg-[#13b19e] px-4 py-2.5 text-xs
|
||||||
font-bold text-[#05111d] transition shadow-lg shadow-[#17d4bd]/20">
|
font-bold text-[#05111d] transition shadow-lg shadow-[#17d4bd]/20">
|
||||||
<Sparkles size={14} className="animate-pulse" />
|
<Sparkles size={14} className="animate-pulse" />
|
||||||
<span>Ask AI</span>
|
<span>Ask AI</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
</NavProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
173
components/dashboard/admin/Configuration/dynamicDashboard.tsx
Normal file
173
components/dashboard/admin/Configuration/dynamicDashboard.tsx
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
import { ChevronDown, Trash2, X, Plus, Sparkles } from "lucide-react";
|
||||||
|
|
||||||
|
const initialRows = [
|
||||||
|
// Row 1 (2 cards)
|
||||||
|
{
|
||||||
|
id: "row-1",
|
||||||
|
widgets: [
|
||||||
|
{ id: "w1", title: "TFM Requests · Monthly", type: "report.widget" },
|
||||||
|
{ id: "w2", title: "Recent Activity", type: "report.widget" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Row 2 (2 cards)
|
||||||
|
{
|
||||||
|
id: "row-2",
|
||||||
|
widgets: [
|
||||||
|
{ id: "w3", title: "Schedule Performance", type: "report.widget" },
|
||||||
|
{ id: "w4", title: "Energy · Today", type: "report.widget" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Row 3 (3 cards)
|
||||||
|
{
|
||||||
|
id: "row-3",
|
||||||
|
widgets: [
|
||||||
|
{ id: "w5", title: "Top 5 Requester", type: "report.widget" },
|
||||||
|
{ id: "w6", title: "Asset Register", type: "report.widget" },
|
||||||
|
{ id: "w7", title: "PM Plan vs Started", type: "report.widget" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Note the 'export default' keyword here:
|
||||||
|
export default function DynamicDashboardPortal() {
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono text-slate-400 mb-1">
|
||||||
|
<span>PLATFORM</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>DYNAMIC DASHBOARD</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-white tracking-tight">
|
||||||
|
Dynamic dashboard
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-2xl leading-relaxed">
|
||||||
|
Design the dashboard each tenant's users land on in Operations. Arrange rows,
|
||||||
|
drop in report widgets, then publish.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="flex items-center gap-3 self-start md:self-auto">
|
||||||
|
<span className="inline-flex items-center gap-1.5 bg-[#0C243B] border border-sky-800/50 text-sky-400 text-xs px-3 py-1.5 rounded-full font-mono font-medium">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-sky-400"></span>
|
||||||
|
PUBLISHED
|
||||||
|
</span>
|
||||||
|
<button className="bg-[#0E1A2B] hover:bg-slate-800 text-slate-200 border border-slate-800 px-4 py-2 rounded-lg text-xs font-semibold transition-colors">
|
||||||
|
Save draft
|
||||||
|
</button>
|
||||||
|
<button className="bg-[#1DE9B6] hover:bg-[#19d4a5] text-slate-950 font-semibold text-xs px-4 py-2 rounded-lg transition-colors shadow-lg shadow-teal-500/10">
|
||||||
|
Publish
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- CONTROL BAR: DESIGNING FOR TENANT --- */}
|
||||||
|
<div className=" border border-slate-800/80 rounded-xl p-2 mb-2 flex flex-col sm:flex-row sm:items-center justify-between gap-4 backdrop-blur-sm">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="text-xs font-medium text-slate-300 shrink-0">
|
||||||
|
Designing for
|
||||||
|
</span>
|
||||||
|
<div className="relative min-w-[200px]">
|
||||||
|
<select
|
||||||
|
defaultValue="Apple One"
|
||||||
|
className="w-full appearance-none bg-[#060B14] border border-slate-800 rounded-lg pl-3.5 pr-8 py-1.5 text-xs text-slate-200 font-medium focus:outline-none focus:border-teal-500/50 cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="Apple One">Apple One</option>
|
||||||
|
<option value="Tenant Two">Tenant Two</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-xs font-mono text-slate-400 bg-[#060B14] border border-slate-800 px-3 py-1.5 rounded-lg shrink-0">
|
||||||
|
3 rows · 7 widgets
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- CANVAS GRID AREA --- */}
|
||||||
|
<div className="border border-slate-800/80 rounded-2xl p-4 sm:p-6 mb-8 space-y-6 bg-[radial-gradient(#1e293b_1px,transparent_1px)] [background-size:16px_16px]">
|
||||||
|
{initialRows.map((row) => (
|
||||||
|
<div key={row.id} className="relative group/row">
|
||||||
|
{/* Delete Row Button */}
|
||||||
|
<button
|
||||||
|
className="absolute -top-3 -right-2 z-10 p-1.5 text-slate-500 hover:text-rose-400 bg-[#060B14] border border-slate-800 rounded-md transition-colors opacity-80 group-hover/row:opacity-100"
|
||||||
|
title="Delete row"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Row Widgets Layout */}
|
||||||
|
<div
|
||||||
|
className={`grid gap-4 ${
|
||||||
|
row.widgets.length === 1
|
||||||
|
? "grid-cols-1"
|
||||||
|
: row.widgets.length === 2
|
||||||
|
? "grid-cols-1 md:grid-cols-2"
|
||||||
|
: "grid-cols-1 md:grid-cols-3"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{row.widgets.map((widget) => (
|
||||||
|
<div
|
||||||
|
key={widget.id}
|
||||||
|
className="bg-[#0B132B]/80 border border-slate-800/90 rounded-xl p-4 backdrop-blur-sm flex flex-col justify-between h-36 relative group/widget hover:border-slate-700/80 transition-all"
|
||||||
|
>
|
||||||
|
{/* Widget Top Header */}
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-white tracking-tight">
|
||||||
|
{widget.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[11px] font-mono text-slate-400 mt-0.5">
|
||||||
|
{widget.type}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="p-1 rounded bg-slate-800/40 hover:bg-slate-700 text-slate-400 hover:text-white transition-colors"
|
||||||
|
title="Remove widget"
|
||||||
|
>
|
||||||
|
<X className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Widget Placeholder Bar Chart */}
|
||||||
|
<div className="flex items-end justify-between gap-1.5 h-12 pt-2 px-1">
|
||||||
|
<div className="w-full bg-[#0E2433] rounded-t-sm h-[30%] border-t border-teal-500/50"></div>
|
||||||
|
<div className="w-full bg-[#0E2433] rounded-t-sm h-[45%] border-t border-teal-500/50"></div>
|
||||||
|
<div className="w-full bg-[#0E2433] rounded-t-sm h-[85%] border-t border-teal-500/50"></div>
|
||||||
|
<div className="w-full bg-[#0E2433] rounded-t-sm h-[60%] border-t border-teal-500/50"></div>
|
||||||
|
<div className="w-full bg-[#0E2433] rounded-t-sm h-[75%] border-t border-teal-500/50"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- BOTTOM ADD ROW CONTROLS --- */}
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<span className="text-xs font-mono text-slate-400 uppercase tracking-wider">
|
||||||
|
Add row:
|
||||||
|
</span>
|
||||||
|
<button className="flex items-center gap-1.5 hover:bg-slate-800 border border-slate-800/80 text-slate-200 text-xs px-3 py-1.5 rounded-lg transition-colors font-medium">
|
||||||
|
<Plus className="w-3.5 h-3.5 text-teal-400" />
|
||||||
|
<span>1 card</span>
|
||||||
|
</button>
|
||||||
|
<button className="flex items-center gap-1.5 hover:bg-slate-800 border border-slate-800/80 text-slate-200 text-xs px-3 py-1.5 rounded-lg transition-colors font-medium">
|
||||||
|
<Plus className="w-3.5 h-3.5 text-teal-400" />
|
||||||
|
<span>2 cards</span>
|
||||||
|
</button>
|
||||||
|
<button className="flex items-center gap-1.5 hover:bg-slate-800 border border-slate-800/80 text-slate-200 text-xs px-3 py-1.5 rounded-lg transition-colors font-medium">
|
||||||
|
<Plus className="w-3.5 h-3.5 text-teal-400" />
|
||||||
|
<span>3 cards</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
256
components/dashboard/admin/Configuration/language.tsx
Normal file
256
components/dashboard/admin/Configuration/language.tsx
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Plus, ChevronDown } from "lucide-react";
|
||||||
|
|
||||||
|
const initialLanguageValues = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
app: "Web Console",
|
||||||
|
menu: "Tenant",
|
||||||
|
screen: "Tenant List",
|
||||||
|
uiKey: "btn.add_tenant",
|
||||||
|
code: "EN",
|
||||||
|
value: "Add Tenant",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
app: "Web Console",
|
||||||
|
menu: "Site",
|
||||||
|
screen: "Site List",
|
||||||
|
uiKey: "btn.add_site",
|
||||||
|
code: "EN",
|
||||||
|
value: "Add Site",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
app: "Mobile App",
|
||||||
|
menu: "Inspections",
|
||||||
|
screen: "QR Round",
|
||||||
|
uiKey: "lbl.scan_qr",
|
||||||
|
code: "EN",
|
||||||
|
value: "Scan QR to begin round",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function LanguagePortal() {
|
||||||
|
|
||||||
|
|
||||||
|
const [languageValues, setLanguageValues] = useState(initialLanguageValues);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans ">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono text-slate-400 mb-1">
|
||||||
|
<span>PLATFORM</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>LANGUAGE</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-bold text-white tracking-tight">Language</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-3xl leading-relaxed">
|
||||||
|
Manage translation keys across apps, menus and screens — then resolve their values per tenant.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- FORM SECTION: LANGUAGE KEY MASTER --- */}
|
||||||
|
<div className=" border border-slate-800/80 rounded-xl
|
||||||
|
p-3 mb-6 shadow-xl backdrop-blur-sm">
|
||||||
|
<div className="mb-5">
|
||||||
|
<h2 className="text-base font-semibold text-white tracking-tight">
|
||||||
|
Language key master
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs font-mono text-slate-400 mt-0.5">
|
||||||
|
Scope a translation key to an app, menu and screen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className="space-y-4">
|
||||||
|
{/* Top Row Selects */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-[11px] font-mono tracking-wider text-slate-400 uppercase mb-1.5">
|
||||||
|
APP
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
|
||||||
|
|
||||||
|
className="w-full appearance-none bg-[#060B14] border border-slate-800 rounded-lg px-3.5 py-2 text-sm text-slate-200 focus:outline-none focus:border-teal-500/50 cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="Web Console">Web Console</option>
|
||||||
|
<option value="Mobile App">Mobile App</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-[11px] font-mono tracking-wider text-slate-400 uppercase mb-1.5">
|
||||||
|
MENU
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
|
||||||
|
|
||||||
|
className="w-full appearance-none bg-[#060B14] border border-slate-800 rounded-lg px-3.5 py-2 text-sm text-slate-200 focus:outline-none focus:border-teal-500/50 cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="Tenant">Tenant</option>
|
||||||
|
<option value="Site">Site</option>
|
||||||
|
<option value="Inspections">Inspections</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-[11px] font-mono tracking-wider text-slate-400 uppercase mb-1.5">
|
||||||
|
SCREEN
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
|
||||||
|
className="w-full appearance-none bg-[#060B14] border border-slate-800 rounded-lg px-3.5 py-2 text-sm text-slate-200 focus:outline-none focus:border-teal-500/50 cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="Tenant List">Tenant List</option>
|
||||||
|
<option value="Site List">Site List</option>
|
||||||
|
<option value="QR Round">QR Round</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Row Inputs & Action Button */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-end">
|
||||||
|
<div className="md:col-span-4">
|
||||||
|
<label className="block text-[11px] font-mono tracking-wider text-slate-400 uppercase mb-1.5">
|
||||||
|
UI KEY
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
|
||||||
|
placeholder="e.g. btn.add_tenant"
|
||||||
|
className="w-full bg-[#060B14] border border-slate-800 rounded-lg px-3.5 py-2 text-sm text-slate-200 font-mono focus:outline-none focus:border-teal-500/50 placeholder:text-slate-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<label className="block text-[11px] font-mono tracking-wider text-slate-400 uppercase mb-1.5">
|
||||||
|
CODE
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
|
||||||
|
className="w-full appearance-none bg-[#060B14] border border-slate-800 rounded-lg px-3.5 py-2 text-sm text-slate-200 focus:outline-none focus:border-teal-500/50 cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="en">en</option>
|
||||||
|
<option value="es">es</option>
|
||||||
|
<option value="fr">fr</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-4">
|
||||||
|
<label className="block text-[11px] font-mono tracking-wider text-slate-400 uppercase mb-1.5">
|
||||||
|
VALUE
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
|
||||||
|
placeholder="e.g. Add Tenant"
|
||||||
|
className="w-full bg-[#060B14] border border-slate-800 rounded-lg px-3.5 py-2 text-sm text-slate-200 focus:outline-none focus:border-teal-500/50 placeholder:text-slate-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-[#1DE9B6] hover:bg-[#19d4a5] text-slate-950 font-semibold text-sm px-4 py-2 rounded-lg flex items-center justify-center gap-1.5 transition-colors shadow-lg shadow-teal-500/10 cursor-pointer"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 stroke-[2.5]" />
|
||||||
|
<span>Add</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- TABLE SECTION: LANGUAGE VALUES --- */}
|
||||||
|
<div className=" border border-slate-800/80 rounded-xl p-3 shadow-xl
|
||||||
|
backdrop-blur-sm">
|
||||||
|
{/* Table Header Controls */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-2">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold text-white tracking-tight">
|
||||||
|
Language values
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs font-mono text-slate-400 mt-0.5">
|
||||||
|
Resolved strings — filtered to the selected tenant's locales
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative min-w-[180px]">
|
||||||
|
<select
|
||||||
|
|
||||||
|
className="w-full appearance-none bg-[#060B14] border border-slate-800 rounded-lg px-3.5 py-1.5 text-sm text-slate-200 focus:outline-none focus:border-teal-500/50 cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="Apple One">Apple One</option>
|
||||||
|
<option value="Tenant Two">Tenant Two</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Locale Tag */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<span className="inline-flex items-center gap-1.5 bg-[#0C243B] border border-sky-800/50 text-sky-400 text-xs px-2.5 py-0.5 rounded-full font-mono font-medium">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-sky-400"></span>
|
||||||
|
EN
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Data Table */}
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-slate-800 text-[10px] font-mono tracking-wider text-slate-400 uppercase">
|
||||||
|
<th className="py-3 px-2 font-medium">APP</th>
|
||||||
|
<th className="py-3 px-2 font-medium">MENU</th>
|
||||||
|
<th className="py-3 px-2 font-medium">SCREEN</th>
|
||||||
|
<th className="py-3 px-2 font-medium">UI KEY</th>
|
||||||
|
<th className="py-3 px-2 font-medium">CODE</th>
|
||||||
|
<th className="py-3 px-2 font-medium">VALUE</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-slate-800/60 text-sm">
|
||||||
|
{languageValues.map((row) => (
|
||||||
|
<tr key={row.id} className="hover:bg-slate-800/20 transition-colors">
|
||||||
|
<td className="py-3.5 px-2 font-medium text-slate-200">{row.app}</td>
|
||||||
|
<td className="py-3.5 px-2 font-medium text-slate-200">{row.menu}</td>
|
||||||
|
<td className="py-3.5 px-2 text-slate-300">{row.screen}</td>
|
||||||
|
<td className="py-3.5 px-2">
|
||||||
|
<span className="font-mono text-xs text-slate-300 bg-[#060B14] border border-slate-800/80 px-2 py-1 rounded">
|
||||||
|
{row.uiKey}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-3.5 px-2">
|
||||||
|
<span className="inline-flex items-center gap-1.5 bg-[#0C243B] border border-sky-800/50 text-sky-400 text-xs px-2 py-0.5 rounded-full font-mono font-medium">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-sky-400"></span>
|
||||||
|
{row.code}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-3.5 px-2 font-semibold text-white">{row.value}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
45
components/dashboard/admin/Configuration/mobileFlows.tsx
Normal file
45
components/dashboard/admin/Configuration/mobileFlows.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Smartphone } from "lucide-react";
|
||||||
|
|
||||||
|
export default function MobileFlowsPortal() {
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
{/* Breadcrumb */}
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono text-slate-400 mb-1">
|
||||||
|
<span>PLATFORM</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>MOBILE</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h1 className="text-2xl font-bold text-white tracking-tight">
|
||||||
|
KAFM Field — mobile flows
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Subtitle / Description */}
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-2xl leading-relaxed">
|
||||||
|
The technician mobile app, embedded live: two flow variations (1a Field Deck · dark, 1b Day Runner · light), 17 screens each — jobs, checklists, QR, inventory, chat, incidents, reports. Every phone is fully interactive.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Button */}
|
||||||
|
<div className="self-start md:self-auto shrink-0">
|
||||||
|
<button className="flex items-center gap-2 bg-[#0A1628] hover:bg-slate-800 text-slate-200 border border-slate-700/80 px-4 py-2 rounded-lg text-xs font-semibold transition-colors shadow-sm cursor-pointer">
|
||||||
|
<Smartphone className="w-4 h-4 text-slate-300" />
|
||||||
|
<span>Open full screen</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- EMBEDDED PHONE DISPLAY AREA --- */}
|
||||||
|
<div className="w-full bg-[#EAEFF4] rounded-2xl min-h-[500px] border border-slate-800 shadow-2xl p-6 flex items-center justify-center">
|
||||||
|
{/* Mobile preview frame container goes here */}
|
||||||
|
<p className="text-slate-500 font-mono text-xs">
|
||||||
|
[ Live Mobile App Frames ]
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
219
components/dashboard/admin/Configuration/modules.tsx
Normal file
219
components/dashboard/admin/Configuration/modules.tsx
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
LayoutGrid,
|
||||||
|
CheckSquare,
|
||||||
|
Crosshair,
|
||||||
|
FileText,
|
||||||
|
Box,
|
||||||
|
Zap,
|
||||||
|
ClipboardCheck,
|
||||||
|
BarChart2,
|
||||||
|
Pencil,
|
||||||
|
Plus,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
// Mock Data matching the design
|
||||||
|
const initialModules = [
|
||||||
|
{
|
||||||
|
id: "MOD-01",
|
||||||
|
title: "Dashboard",
|
||||||
|
description: "Operational overview & live KPIs",
|
||||||
|
icon: LayoutGrid,
|
||||||
|
menus: [], // Currently empty as per screenshot
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MOD-02",
|
||||||
|
title: "Helpdesk",
|
||||||
|
description: "Tickets, requests & SLA tracking",
|
||||||
|
icon: CheckSquare,
|
||||||
|
menus: ["Tickets", "SLA Settings", "Queues"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MOD-03",
|
||||||
|
title: "Equipment",
|
||||||
|
description: "Asset register & equipment groups",
|
||||||
|
icon: Crosshair,
|
||||||
|
menus: ["Asset Register", "Categories"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MOD-04",
|
||||||
|
title: "Request",
|
||||||
|
description: "Service requests & approvals",
|
||||||
|
icon: FileText,
|
||||||
|
menus: ["My Requests", "Approvals"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MOD-05",
|
||||||
|
title: "Inventory",
|
||||||
|
description: "Stock, spares & consumables",
|
||||||
|
icon: Box,
|
||||||
|
menus: ["Stock Levels", "Suppliers"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MOD-06",
|
||||||
|
title: "Energy",
|
||||||
|
description: "Meters, consumption & analytics",
|
||||||
|
icon: Zap,
|
||||||
|
menus: ["Meters", "Analytics"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MOD-07",
|
||||||
|
title: "Inspections",
|
||||||
|
description: "QR rounds & checklists",
|
||||||
|
icon: ClipboardCheck,
|
||||||
|
menus: ["Rounds", "Checklists"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "MOD-08",
|
||||||
|
title: "Reports",
|
||||||
|
description: "Analytics & export tools",
|
||||||
|
icon: BarChart2,
|
||||||
|
menus: ["Scheduled Reports", "Custom Builder"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function ModulesPortal() {
|
||||||
|
const [selectedModuleId, setSelectedModuleId] = useState("MOD-01");
|
||||||
|
|
||||||
|
const activeModule = initialModules.find((m) => m.id === selectedModuleId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-saas">
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-semibold tracking-wider text-slate-400 uppercase font-mono mb-1">
|
||||||
|
<span>PLATFORM</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>MODULES</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-bold text-white tracking-tight">Modules</h1>
|
||||||
|
<p className="text-sm text-slate-400 mt-1 max-w-2xl">
|
||||||
|
The catalog of system modules and their menus. These are what you switch on per tenant under feature access.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Grid Container */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">
|
||||||
|
{/* Left Column: Modules List */}
|
||||||
|
<div className="lg:col-span-6 bg-[#0E1A2B]/60 border border-slate-800/80 rounded-xl p-5 backdrop-blur-sm">
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold text-white">Modules</h2>
|
||||||
|
<span className="text-xs font-mono text-slate-400">
|
||||||
|
{initialModules.length} available
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button className="flex items-center gap-1.5 text-emerald-400 hover:text-emerald-300 text-xs font-semibold transition-colors">
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
<span>Add module</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Module Items */}
|
||||||
|
<div className="space-y-2 max-h-[620px] overflow-y-auto pr-1 custom-scrollbar">
|
||||||
|
{initialModules.map((module) => {
|
||||||
|
const Icon = module.icon;
|
||||||
|
const isSelected = module.id === selectedModuleId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={module.id}
|
||||||
|
onClick={() => setSelectedModuleId(module.id)}
|
||||||
|
className={`group relative flex items-center justify-between p-3.5 rounded-lg border cursor-pointer transition-all ${
|
||||||
|
isSelected
|
||||||
|
? "bg-teal-950/30 border-teal-500/40"
|
||||||
|
: "bg-[#09111E]/40 border-slate-800/60 hover:border-slate-700 hover:bg-[#0D1726]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3.5 min-w-0">
|
||||||
|
<div
|
||||||
|
className={`p-2.5 rounded-lg border shrink-0 ${
|
||||||
|
isSelected
|
||||||
|
? "bg-teal-500/10 border-teal-500/30 text-teal-400"
|
||||||
|
: "bg-slate-900/80 border-slate-800 text-slate-400 group-hover:text-slate-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
<div className="truncate">
|
||||||
|
<h3 className="text-sm font-semibold text-white truncate">
|
||||||
|
{module.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-slate-400 truncate mt-0.5">
|
||||||
|
{module.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 shrink-0 ml-3">
|
||||||
|
<span className="text-[11px] font-mono bg-slate-900 border border-slate-800 text-slate-400 px-2 py-0.5 rounded">
|
||||||
|
{module.id}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// Edit logic here
|
||||||
|
}}
|
||||||
|
className="p-1.5 text-slate-400 hover:text-white rounded-md hover:bg-slate-800 transition-colors"
|
||||||
|
title="Edit module"
|
||||||
|
>
|
||||||
|
<Pencil className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column: Menus Panel */}
|
||||||
|
<div className="lg:col-span-6 bg-[#0E1A2B]/60 border border-slate-800/80 rounded-xl p-5 min-h-[500px] flex flex-col justify-between backdrop-blur-sm">
|
||||||
|
<div>
|
||||||
|
{/* Menus Header */}
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold text-white">Menus</h2>
|
||||||
|
<span className="text-xs font-mono text-slate-400">
|
||||||
|
Under {activeModule?.title || "Module"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button className="flex items-center gap-1.5 text-teal-400 hover:text-teal-300 text-xs font-semibold transition-colors">
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
<span>Add menu</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Empty State / Menus List */}
|
||||||
|
{activeModule?.menus && activeModule.menus.length > 0 ? (
|
||||||
|
<div className="space-y-2 mt-4">
|
||||||
|
{activeModule.menus.map((menu, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="flex items-center justify-between p-3 rounded-lg bg-[#09111E]/40 border border-slate-800/60"
|
||||||
|
>
|
||||||
|
<span className="text-sm font-medium text-slate-200">{menu}</span>
|
||||||
|
<button className="text-xs text-slate-400 hover:text-white">
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center justify-center h-80 text-center">
|
||||||
|
<p className="text-sm text-slate-400 font-medium">
|
||||||
|
No menus under this module yet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
190
components/dashboard/admin/Platform/overview.tsx
Normal file
190
components/dashboard/admin/Platform/overview.tsx
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
ClipboardCheck,
|
||||||
|
Clock,
|
||||||
|
AlertTriangle,
|
||||||
|
Activity,
|
||||||
|
Building2,
|
||||||
|
Users,
|
||||||
|
MapPin,
|
||||||
|
Grid,
|
||||||
|
ArrowRight,
|
||||||
|
Sparkles
|
||||||
|
} from "lucide-react";
|
||||||
|
import StatCard from "@/components/common/dashStatCard";
|
||||||
|
|
||||||
|
export default function AdminOverviewPortal() {
|
||||||
|
// Sample data for the recent audits table below
|
||||||
|
const recentAudits = [
|
||||||
|
{ id: 'AUD-1004', location: 'Local 1', lead: 'Local Admin', date: '2026-02-02', status: 'ACTIVE' },
|
||||||
|
{ id: 'AUD-1003', location: 'SRM Campus', lead: 'Estates Office', date: '2026-01-08', status: 'ACTIVE' },
|
||||||
|
{ id: 'AUD-1002', location: 'Novotel Pune', lead: 'Site FM Lead', date: '2025-12-12', status: 'ACTIVE' },
|
||||||
|
{ id: 'AUD-1001', location: 'Apple One', lead: 'Dhananjay T.', date: '2025-11-04', status: 'ACTIVE' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Sample data for module/checklist adoption progress bars
|
||||||
|
const adoptionModules = [
|
||||||
|
{ name: 'Dashboard', score: 100, count: '4/4' },
|
||||||
|
{ name: 'Helpdesk', score: 75, count: '3/4' },
|
||||||
|
{ name: 'Equipment', score: 75, count: '3/4' },
|
||||||
|
{ name: 'Request', score: 75, count: '3/4' },
|
||||||
|
{ name: 'Inventory', score: 50, count: '2/4' },
|
||||||
|
{ name: 'Energy', score: 75, count: '3/4' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans ">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono">
|
||||||
|
<span className="text-teal-400">PLATFORM</span>
|
||||||
|
<span className="text-slate-500">•</span>
|
||||||
|
<span className="text-slate-500">KAFM</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-white tracking-tight mt-1">
|
||||||
|
Platform overview
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-xl leading-relaxed">
|
||||||
|
Live snapshot of every organization, user, and site managed across the KAFM facility platform.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top Right Status Badge */}
|
||||||
|
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-slate-900/80 border border-teal-500/30 text-teal-400 text-xs font-mono w-fit">
|
||||||
|
<span className="h-2 w-2 rounded-full bg-teal-400 animate-pulse" />
|
||||||
|
<span>PRODUCTION</span>
|
||||||
|
<span className="text-slate-600">•</span>
|
||||||
|
<span className="text-teal-300/80">region us-east-1</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- STAT CARDS --- */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
|
{/* Card 1: Total Tenants */}
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="Total tenants"
|
||||||
|
value="4"
|
||||||
|
icon={Building2}
|
||||||
|
badgeText="4 active"
|
||||||
|
badgeClassName="text-teal-400 font-mono text-xs"
|
||||||
|
iconContainerClassName="bg-teal-950/40 border-teal-800/30 text-teal-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Card 2: Platform users */}
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="Platform users"
|
||||||
|
value="6"
|
||||||
|
icon={Users}
|
||||||
|
badgeText="+2 this wk"
|
||||||
|
badgeClassName="text-teal-400 font-mono text-xs"
|
||||||
|
iconContainerClassName="bg-slate-800/50 border-slate-700/50 text-blue-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Card 3: Managed sites */}
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="Managed sites"
|
||||||
|
value="6"
|
||||||
|
icon={MapPin}
|
||||||
|
badgeText="6 live"
|
||||||
|
badgeClassName="text-teal-400 font-mono text-xs"
|
||||||
|
iconContainerClassName="bg-amber-950/30 border-amber-800/30 text-amber-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Card 4: System modules */}
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="System modules"
|
||||||
|
value="13"
|
||||||
|
icon={Grid}
|
||||||
|
badgeText="all enabled"
|
||||||
|
badgeClassName="text-slate-400 font-mono text-xs"
|
||||||
|
iconContainerClassName="bg-purple-950/40 border-purple-800/30 text-purple-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- LOWER SECTION (ADDITION FROM SCREENSHOT) --- */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
|
||||||
|
{/* Left 2-Columns: Recently Onboarded Tenants Table */}
|
||||||
|
<div className="lg:col-span-2 bg-slate-900/60 border border-slate-800/80 rounded-xl p-5 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold text-white">Recently onboarded tenants</h2>
|
||||||
|
<p className="text-xs text-slate-400 font-mono mt-0.5">Newest organizations on the platform</p>
|
||||||
|
</div>
|
||||||
|
<button className="text-teal-400 hover:text-teal-300 text-xs font-semibold flex items-center gap-1 transition-colors">
|
||||||
|
View all <ArrowRight className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Table Header */}
|
||||||
|
<div className="grid grid-cols-12 text-[11px] font-mono text-slate-500 uppercase tracking-wider py-2 border-b border-slate-800/80">
|
||||||
|
<span className="col-span-4">Organization</span>
|
||||||
|
<span className="col-span-3">Tenant ID</span>
|
||||||
|
<span className="col-span-3">Created</span>
|
||||||
|
<span className="col-span-2 text-right">Status</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Table Rows */}
|
||||||
|
<div className="divide-y divide-slate-800/40">
|
||||||
|
{recentAudits.map((item, index) => (
|
||||||
|
<div key={index} className="grid grid-cols-12 items-center py-3 text-xs">
|
||||||
|
<div className="col-span-4 flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-lg bg-slate-800 border border-slate-700/60 flex items-center justify-center font-bold text-slate-300 text-xs">
|
||||||
|
{item.location.substring(0, 2).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-200">{item.location}</div>
|
||||||
|
<div className="text-[11px] text-slate-400">{item.lead}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="col-span-3 font-mono text-slate-400 text-xs">{item.id}</span>
|
||||||
|
<span className="col-span-3 font-mono text-slate-400 text-xs">{item.date}</span>
|
||||||
|
<div className="col-span-2 flex justify-end">
|
||||||
|
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-mono font-bold bg-teal-950/80 text-teal-400 border border-teal-800/50">
|
||||||
|
<span className="h-1.5 w-1.5 rounded-full bg-teal-400"></span>
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right 1-Column: Module Adoption */}
|
||||||
|
<div className="bg-slate-900/60 border border-slate-800/80 rounded-xl p-5 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold text-white mb-5">Module adoption</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{adoptionModules.map((module, idx) => (
|
||||||
|
<div key={idx} className="space-y-1.5">
|
||||||
|
<div className="flex justify-between items-center text-xs">
|
||||||
|
<span className="font-semibold text-slate-200">{module.name}</span>
|
||||||
|
<span className="font-mono text-slate-400 text-[11px]">{module.count}</span>
|
||||||
|
</div>
|
||||||
|
{/* Progress Bar Container */}
|
||||||
|
<div className="w-full bg-slate-800/80 h-2 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="bg-teal-400 h-full rounded-full transition-all duration-500"
|
||||||
|
style={{ width: `${module.score}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
416
components/dashboard/admin/Platform/security.tsx
Normal file
416
components/dashboard/admin/Platform/security.tsx
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Shield,
|
||||||
|
Users,
|
||||||
|
AlertTriangle,
|
||||||
|
ShieldCheck,
|
||||||
|
BarChart3,
|
||||||
|
Sparkles,
|
||||||
|
ChevronDown
|
||||||
|
} from "lucide-react";
|
||||||
|
import Button from "@/components/common/button";
|
||||||
|
import StatCard from "@/components/common/dashStatCard";
|
||||||
|
import InfoBanner from '@/components/common/infoBanner';
|
||||||
|
import Table from '@/components/common/dataTable';
|
||||||
|
import { StatusBadge } from "@/components/common/statusBadge";
|
||||||
|
|
||||||
|
export const trustedDeviceHeaders = [
|
||||||
|
"USER",
|
||||||
|
"DEVICE",
|
||||||
|
"IP",
|
||||||
|
"LAST SEEN",
|
||||||
|
"EXPIRES",
|
||||||
|
"STATUS",
|
||||||
|
"ACTIONS",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const trustedDeviceRows = [
|
||||||
|
[
|
||||||
|
<span className="font-semibold text-white">Dhananjay T.</span>,
|
||||||
|
"Chrome 126 · Windows 11",
|
||||||
|
<span className="font-mono">103.212.14.8</span>,
|
||||||
|
<span className="font-mono">Today, 08:12</span>,
|
||||||
|
<span className="font-mono">Jul 30, 2026</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="TRUSTED"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
<Button className="rounded-md border border-slate-700/80 bg-slate-800/40 px-3 py-1 text-xs text-slate-300 hover:bg-slate-800 transition-colors">
|
||||||
|
Revoke
|
||||||
|
</Button>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-semibold text-white">Kavya Raghunathan</span>,
|
||||||
|
"Safari 18 · macOS",
|
||||||
|
<span className="font-mono">49.37.201.55</span>,
|
||||||
|
<span className="font-mono">Yesterday, 17:40</span>,
|
||||||
|
<span className="font-mono">Jul 21, 2026</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="TRUSTED"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
<Button className="rounded-md border border-slate-700/80 bg-slate-800/40 px-3 py-1 text-xs text-slate-300 hover:bg-slate-800 transition-colors">
|
||||||
|
Revoke
|
||||||
|
</Button>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-semibold text-white">Rahul Iyer</span>,
|
||||||
|
"KAFM Mobile · Android 15",
|
||||||
|
<span className="font-mono">152.58.33.104</span>,
|
||||||
|
<span className="font-mono">Today, 06:58</span>,
|
||||||
|
<span className="font-mono">Jul 12, 2026</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="TRUSTED"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
<Button className="rounded-md border border-slate-700/80 bg-slate-800/40 px-3 py-1 text-xs text-slate-300 hover:bg-slate-800 transition-colors">
|
||||||
|
Revoke
|
||||||
|
</Button>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-semibold text-white">Meera Pillai</span>,
|
||||||
|
"Edge 126 · Windows 10",
|
||||||
|
<span className="font-mono">103.212.14.31</span>,
|
||||||
|
<span className="font-mono">Jun 18, 2026</span>,
|
||||||
|
<span className="font-mono text-slate-400">Expired</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="REVOKED"
|
||||||
|
color="bg-slate-700/50 text-slate-400"
|
||||||
|
/>,
|
||||||
|
<span className="text-slate-500 text-center">—</span>,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const auditLogHeaders = [
|
||||||
|
"WHEN",
|
||||||
|
"USER",
|
||||||
|
"EVENT",
|
||||||
|
"IP",
|
||||||
|
"RESULT",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const auditLogRows = [
|
||||||
|
[
|
||||||
|
<span className="font-mono text-slate-300">Today 08:12</span>,
|
||||||
|
<span className="font-mono text-slate-300">dhananjay@kafm.io</span>,
|
||||||
|
<span className="text-slate-200">Sign-in · password + MFA</span>,
|
||||||
|
<span className="font-mono text-slate-300">103.212.14.8</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="OK"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-mono text-slate-300">Today 07:56</span>,
|
||||||
|
<span className="font-mono text-slate-300">kavya@kafm.io</span>,
|
||||||
|
<span className="text-slate-200">
|
||||||
|
Role changed · Technician → M&E Supervisor
|
||||||
|
</span>,
|
||||||
|
<span className="font-mono text-slate-300">49.37.201.55</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="OK"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-mono text-slate-300">Today 07:31</span>,
|
||||||
|
<span className="font-mono text-slate-300">unknown@ext.net</span>,
|
||||||
|
<span className="text-slate-200">
|
||||||
|
Sign-in · wrong password (3rd attempt)
|
||||||
|
</span>,
|
||||||
|
<span className="font-mono text-slate-300">91.240.118.7</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="FAILED"
|
||||||
|
color="bg-rose-500/15 text-rose-400"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-mono text-slate-300">Today 06:58</span>,
|
||||||
|
<span className="font-mono text-slate-300">rahul@kafm.io</span>,
|
||||||
|
<span className="text-slate-200">
|
||||||
|
Device trusted · KAFM Mobile (30 days)
|
||||||
|
</span>,
|
||||||
|
<span className="font-mono text-slate-300">152.58.33.104</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="OK"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-mono text-slate-300">Yesterday 17:40</span>,
|
||||||
|
<span className="font-mono text-slate-300">kavya@kafm.io</span>,
|
||||||
|
<span className="text-slate-200">
|
||||||
|
Password reset · self-service link
|
||||||
|
</span>,
|
||||||
|
<span className="font-mono text-slate-300">49.37.201.55</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="OK"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-mono text-slate-300">Yesterday 14:05</span>,
|
||||||
|
<span className="font-mono text-slate-300">meera@kafm.io</span>,
|
||||||
|
<span className="text-slate-200">
|
||||||
|
MFA reset by admin · device revoked
|
||||||
|
</span>,
|
||||||
|
<span className="font-mono text-slate-300">103.212.14.31</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="PENDING"
|
||||||
|
color="bg-amber-500/15 text-amber-400"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<span className="font-mono text-slate-300">Jun 30 09:12</span>,
|
||||||
|
<span className="font-mono text-slate-300">admin@kafm.io</span>,
|
||||||
|
<span className="text-slate-200">
|
||||||
|
Sign-in policy updated · idle timeout 30 min
|
||||||
|
</span>,
|
||||||
|
<span className="font-mono text-slate-300">103.212.14.8</span>,
|
||||||
|
<StatusBadge
|
||||||
|
text="OK"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function SecurityPortal() {
|
||||||
|
const [enforceMfa, setEnforceMfa] = useState(true);
|
||||||
|
const [keepMeSignedIn, setKeepMeSignedIn] = useState(true);
|
||||||
|
const [reAuthenticate, setReAuthenticate] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans ">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono">
|
||||||
|
<span className="text-teal-400">PLATFORM</span>
|
||||||
|
<span className="text-slate-500">•</span>
|
||||||
|
<span className="text-slate-500">SECURITY</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-bold text-white tracking-tight mt-1">
|
||||||
|
Security & sessions
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-2xl leading-relaxed">
|
||||||
|
Platform-wide sign-in policy, trusted devices and the immutable authentication audit trail. Sign-ins, MFA, resets and policy changes are logged automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- HEADER BUTTONS --- */}
|
||||||
|
<div className="flex items-center gap-3 self-start md:mt-2">
|
||||||
|
<Button className="bg-slate-900/80 hover:bg-slate-800 text-slate-200 border border-slate-700/60 text-xs px-4 py-2.5 rounded-lg flex items-center gap-2 font-semibold transition-colors">
|
||||||
|
<BarChart3 className="w-4 h-4 text-slate-300" />
|
||||||
|
<span>Export log</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button className="bg-emerald-400 hover:bg-emerald-300 text-slate-950 font-semibold text-xs px-4 py-2.5 rounded-lg flex items-center gap-2 transition-colors shadow-lg shadow-emerald-500/10">
|
||||||
|
<span>Publish policy</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- STAT CARDS --- */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="MFA coverage"
|
||||||
|
value="83%"
|
||||||
|
icon={Shield}
|
||||||
|
badgeText="of platform users"
|
||||||
|
badgeClassName="text-amber-400 lowercase font-mono"
|
||||||
|
iconContainerClassName="bg-emerald-950/40 border-emerald-800/30 text-emerald-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="Active sessions"
|
||||||
|
value="12"
|
||||||
|
icon={Users}
|
||||||
|
badgeText="right now"
|
||||||
|
badgeClassName="text-slate-400 lowercase font-mono"
|
||||||
|
iconContainerClassName="bg-slate-800/50 border-slate-700/40 text-sky-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="Failed sign-ins"
|
||||||
|
value="3"
|
||||||
|
icon={AlertTriangle}
|
||||||
|
badgeText="last 24 h"
|
||||||
|
badgeClassName="text-rose-400 lowercase font-mono"
|
||||||
|
iconContainerClassName="bg-rose-950/40 border-rose-800/30 text-rose-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StatCard
|
||||||
|
variant="icon"
|
||||||
|
title="Trusted devices"
|
||||||
|
value="3"
|
||||||
|
icon={ShieldCheck}
|
||||||
|
badgeText="MFA skipped"
|
||||||
|
badgeClassName="text-slate-400 lowercase font-mono"
|
||||||
|
iconContainerClassName="bg-amber-950/40 border-amber-800/30 text-amber-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- TOP GRID SECTION: AUDIT LOG + SIGN-IN POLICY --- */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 mb-6">
|
||||||
|
|
||||||
|
{/* --- LEFT: AUDIT LOG TABLE CONTAINER --- */}
|
||||||
|
<div className="lg:col-span-8 bg-slate-900/60 border border-slate-800/80 rounded-xl p-5 flex flex-col justify-between space-y-4">
|
||||||
|
<div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<h2 className="text-base font-semibold text-white tracking-tight">
|
||||||
|
Authentication audit trail
|
||||||
|
</h2>
|
||||||
|
<div className="flex items-center gap-2 text-[11px] font-mono text-slate-400 mt-0.5">
|
||||||
|
<span>immutable</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>90-day retention</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* AUDIT LOG TABLE */}
|
||||||
|
<Table headers={auditLogHeaders} rows={auditLogRows} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- RIGHT: SIGN-IN POLICY PANEL --- */}
|
||||||
|
<div className="lg:col-span-4 bg-slate-900/60 border border-slate-800/80 rounded-xl p-5 space-y-6">
|
||||||
|
<h2 className="text-base font-semibold text-white tracking-tight">
|
||||||
|
Sign-in policy
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6 text-sm">
|
||||||
|
{/* Toggle 1: Enforce MFA */}
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Enforce MFA for all users</p>
|
||||||
|
<p className="text-xs text-slate-400 leading-snug mt-0.5">
|
||||||
|
6-digit code required on every untrusted device
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setEnforceMfa(!enforceMfa)}
|
||||||
|
className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${enforceMfa ? 'bg-teal-400' : 'bg-slate-800'}`}
|
||||||
|
>
|
||||||
|
<span className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-slate-950 shadow ring-0 transition duration-200 ease-in-out ${enforceMfa ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Select 1: Idle Session Timeout */}
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Idle session timeout</p>
|
||||||
|
<p className="text-xs text-slate-400 leading-snug mt-0.5">
|
||||||
|
Auto sign-out after inactivity
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="relative shrink-0">
|
||||||
|
<select className="appearance-none bg-slate-950/80 border border-slate-800 rounded-lg pl-3 pr-8 py-1.5 text-xs text-slate-200 font-medium focus:outline-none focus:border-slate-700 cursor-pointer">
|
||||||
|
<option value="30">30 minutes</option>
|
||||||
|
<option value="15">15 minutes</option>
|
||||||
|
<option value="60">1 hour</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toggle 2: Keep me signed in */}
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">“Keep me signed in” by default</p>
|
||||||
|
<p className="text-xs text-slate-400 leading-snug mt-0.5">
|
||||||
|
Pre-checks the persistent-session option at sign-in
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setKeepMeSignedIn(!keepMeSignedIn)}
|
||||||
|
className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${keepMeSignedIn ? 'bg-teal-400' : 'bg-slate-800'}`}
|
||||||
|
>
|
||||||
|
<span className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-slate-950 shadow ring-0 transition duration-200 ease-in-out ${keepMeSignedIn ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toggle 3: Re-authenticate */}
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Re-authenticate for sensitive actions</p>
|
||||||
|
<p className="text-xs text-slate-400 leading-snug mt-0.5">
|
||||||
|
Password prompt before user, role or policy changes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setReAuthenticate(!reAuthenticate)}
|
||||||
|
className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${reAuthenticate ? 'bg-teal-400' : 'bg-slate-800'}`}
|
||||||
|
>
|
||||||
|
<span className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-slate-950 shadow ring-0 transition duration-200 ease-in-out ${reAuthenticate ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Select 2: Trusted device window */}
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Trusted device window</p>
|
||||||
|
<p className="text-xs text-slate-400 leading-snug mt-0.5">
|
||||||
|
Days MFA is skipped on a trusted device
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="relative shrink-0">
|
||||||
|
<select className="appearance-none bg-slate-950/80 border border-slate-800 rounded-lg pl-3 pr-8 py-1.5 text-xs text-slate-200 font-medium focus:outline-none focus:border-slate-700 cursor-pointer">
|
||||||
|
<option value="30">30</option>
|
||||||
|
<option value="60">60</option>
|
||||||
|
<option value="90">90</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- BOTTOM SECTION: TRUSTED DEVICES TABLE CONTAINER --- */}
|
||||||
|
<div className="bg-slate-900/60 border border-slate-800/80 rounded-xl p-5 mb-6 space-y-4">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold text-white tracking-tight">
|
||||||
|
Trusted devices
|
||||||
|
</h2>
|
||||||
|
<p className="text-[11px] font-mono text-slate-400 mt-0.5">
|
||||||
|
MFA skipped until expiry
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button className="rounded-md border border-slate-700/80 bg-slate-800/50 px-3.5 py-1.5 text-xs font-semibold text-slate-200 hover:bg-slate-800 transition-colors">
|
||||||
|
Revoke all
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TRUSTED DEVICES TABLE */}
|
||||||
|
<Table headers={trustedDeviceHeaders} rows={trustedDeviceRows} />
|
||||||
|
|
||||||
|
<p className="text-xs text-slate-400 pt-1">
|
||||||
|
Revoking forces MFA on the next sign-in from that device. Password changes revoke all devices automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- INFO BANNER --- */}
|
||||||
|
<div className="mt-6">
|
||||||
|
<InfoBanner
|
||||||
|
boldText='Every event is logged:'
|
||||||
|
normalText='the audit trail above updates live — try signing out and back in, requesting a password reset, or trusting a device on the login screen.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
228
components/dashboard/admin/Platform/site.tsx
Normal file
228
components/dashboard/admin/Platform/site.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Building2,
|
||||||
|
Users,
|
||||||
|
MapPin,
|
||||||
|
Grid,
|
||||||
|
Plus,
|
||||||
|
Eye,
|
||||||
|
Settings,
|
||||||
|
Pencil,
|
||||||
|
GitBranch,
|
||||||
|
Search,
|
||||||
|
ChevronDown,
|
||||||
|
Sparkles
|
||||||
|
} from "lucide-react";
|
||||||
|
import Button from "@/components/common/button";
|
||||||
|
|
||||||
|
import Table from '@/components/common/dataTable';
|
||||||
|
import { StatusBadge } from '@/components/common/statusBadge';
|
||||||
|
import ActionButtons from '@/components/common/actionButton';
|
||||||
|
|
||||||
|
export const siteHeaders = [
|
||||||
|
"Site",
|
||||||
|
"Tenant",
|
||||||
|
"Coordinates",
|
||||||
|
"Radius",
|
||||||
|
"Status",
|
||||||
|
"Actions",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const siteRows = [
|
||||||
|
[
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Apple One — HQ Tower</p>
|
||||||
|
<p className="font-mono text-xs text-slate-400">AO-HQ · SIT-3001</p>
|
||||||
|
</div>,
|
||||||
|
"Apple One",
|
||||||
|
<span className="font-mono whitespace-nowrap">19.0760, 72.8777</span>,
|
||||||
|
<span className="rounded-md bg-slate-900 px-2 py-1 font-mono text-xs">200 m</span>,
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="h-7 w-12 rounded-full bg-teal-400 relative">
|
||||||
|
<div className="absolute right-1 top-1 h-5 w-5 rounded-full bg-slate-950" />
|
||||||
|
</div>
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Apple One — Annexe</p>
|
||||||
|
<p className="font-mono text-xs text-slate-400">AO-AX · SIT-3002</p>
|
||||||
|
</div>,
|
||||||
|
"Apple One",
|
||||||
|
<span className="font-mono whitespace-nowrap">19.1020, 72.8895</span>,
|
||||||
|
<span className="rounded-md bg-slate-900 px-2 py-1 font-mono text-xs">150 m</span>,
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="h-7 w-12 rounded-full bg-teal-400 relative">
|
||||||
|
<div className="absolute right-1 top-1 h-5 w-5 rounded-full bg-slate-950" />
|
||||||
|
</div>
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Novotel Pune — Main</p>
|
||||||
|
<p className="font-mono text-xs text-slate-400">NP-M · SIT-3003</p>
|
||||||
|
</div>,
|
||||||
|
"Novotel Pune",
|
||||||
|
<span className="font-mono whitespace-nowrap">18.5604, 73.9090</span>,
|
||||||
|
<span className="rounded-md bg-slate-900 px-2 py-1 font-mono text-xs">180 m</span>,
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="h-7 w-12 rounded-full bg-teal-400 relative">
|
||||||
|
<div className="absolute right-1 top-1 h-5 w-5 rounded-full bg-slate-950" />
|
||||||
|
</div>
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">
|
||||||
|
SRM Campus — Academic Block
|
||||||
|
</p>
|
||||||
|
<p className="font-mono text-xs text-slate-400">SRM-AB · SIT-3004</p>
|
||||||
|
</div>,
|
||||||
|
"SRM Campus",
|
||||||
|
<span className="font-mono whitespace-nowrap">12.8230, 80.0444</span>,
|
||||||
|
<span className="rounded-md bg-slate-900 px-2 py-1 font-mono text-xs">400 m</span>,
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="h-7 w-12 rounded-full bg-teal-400 relative">
|
||||||
|
<div className="absolute right-1 top-1 h-5 w-5 rounded-full bg-slate-950" />
|
||||||
|
</div>
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">
|
||||||
|
SRM Campus — Hostel Block
|
||||||
|
</p>
|
||||||
|
<p className="font-mono text-xs text-slate-400">SRM-HB · SIT-3005</p>
|
||||||
|
</div>,
|
||||||
|
"SRM Campus",
|
||||||
|
<span className="font-mono whitespace-nowrap">12.8255, 80.0478</span>,
|
||||||
|
<span className="rounded-md bg-slate-900 px-2 py-1 font-mono text-xs">350 m</span>,
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="h-7 w-12 rounded-full bg-teal-400 relative">
|
||||||
|
<div className="absolute right-1 top-1 h-5 w-5 rounded-full bg-slate-950" />
|
||||||
|
</div>
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-slate-100">Local 1 — Site</p>
|
||||||
|
<p className="font-mono text-xs text-slate-400">L1-S · SIT-3006</p>
|
||||||
|
</div>,
|
||||||
|
"Local 1",
|
||||||
|
<span className="font-mono whitespace-nowrap">19.2183, 72.9781</span>,
|
||||||
|
<span className="rounded-md bg-slate-900 px-2 py-1 font-mono text-xs">120 m</span>,
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="h-7 w-12 rounded-full bg-teal-400 relative">
|
||||||
|
<div className="absolute right-1 top-1 h-5 w-5 rounded-full bg-slate-950" />
|
||||||
|
</div>
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function SitePortal() {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono">
|
||||||
|
<span className="text-teal-400">PLATFORM</span>
|
||||||
|
<span className="text-slate-500">•</span>
|
||||||
|
<span className="text-slate-500">SITES</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-bold text-white tracking-tight mt-1">
|
||||||
|
Sites
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-2xl leading-relaxed">
|
||||||
|
Physical locations under each tenant. Define geofences with map coordinates and a working radius. Operations site pickers read from here.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- HEADER BUTTON --- */}
|
||||||
|
<div className="flex items-center gap-3 self-start md:mt-2">
|
||||||
|
<Button className="bg-emerald-400 hover:bg-emerald-300 text-slate-950 font-semibold text-xs px-4 py-2.5 rounded-lg flex items-center gap-1.5 transition-colors shadow-lg shadow-emerald-500/10">
|
||||||
|
<Plus className="w-4 h-4 text-slate-950 stroke-[3]" />
|
||||||
|
<span>Add site</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- TABLE CONTAINER WITH UPPER BAR --- */}
|
||||||
|
<div className="bg-slate-900/60 border border-slate-800 rounded-xl p-4 space-y-4">
|
||||||
|
{/* Table Upper UI (Search Bar & Filter Dropdown) */}
|
||||||
|
<div className="flex flex-col sm:flex-row items-center gap-3">
|
||||||
|
{/* Search Bar Input */}
|
||||||
|
<div className="relative flex-1 w-full">
|
||||||
|
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="Search sites..."
|
||||||
|
className="w-full pl-10 pr-4 py-2 bg-slate-950/80 border border-slate-800 rounded-lg text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:border-slate-700 transition"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* All Tenants Filter Select Dropdown */}
|
||||||
|
<div className="relative w-full sm:w-44">
|
||||||
|
<select className="w-full appearance-none bg-slate-950/80 border border-slate-800 rounded-lg px-3.5 py-2 pr-8 text-sm text-slate-300 font-medium focus:outline-none focus:border-slate-700 cursor-pointer">
|
||||||
|
<option value="all">All tenants</option>
|
||||||
|
<option value="apple-one">Apple One</option>
|
||||||
|
<option value="novotel">Novotel Pune</option>
|
||||||
|
<option value="srm">SRM Campus</option>
|
||||||
|
<option value="local1">Local 1</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- TABLE --- */}
|
||||||
|
<Table headers={siteHeaders} rows={siteRows} />
|
||||||
|
|
||||||
|
{/* Footer Record Count */}
|
||||||
|
<div className="text-xs text-slate-500 font-mono pt-1">
|
||||||
|
{siteRows.length} records
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
244
components/dashboard/admin/Platform/tenants.tsx
Normal file
244
components/dashboard/admin/Platform/tenants.tsx
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Building2,
|
||||||
|
Users,
|
||||||
|
MapPin,
|
||||||
|
Grid,
|
||||||
|
Plus,
|
||||||
|
Eye,
|
||||||
|
Settings,
|
||||||
|
Pencil,
|
||||||
|
GitBranch,
|
||||||
|
Search,
|
||||||
|
ChevronDown
|
||||||
|
} from "lucide-react";
|
||||||
|
import Button from "@/components/common/button";
|
||||||
|
import StatCard from "@/components/common/dashStatCard";
|
||||||
|
import Table from '@/components/common/dataTable';
|
||||||
|
import { StatusBadge } from '@/components/common/statusBadge';
|
||||||
|
|
||||||
|
export const organizationHeaders = [
|
||||||
|
"Organization",
|
||||||
|
"Tenant ID",
|
||||||
|
"Contact",
|
||||||
|
"Time Zone",
|
||||||
|
"Status",
|
||||||
|
"Actions",
|
||||||
|
];
|
||||||
|
|
||||||
|
const ActionButtons = () => (
|
||||||
|
<div className="flex items-center justify-center gap-1">
|
||||||
|
<Button className="flex h-8 w-8 items-center justify-center rounded-md border border-slate-700/60 text-slate-400 hover:bg-slate-800 hover:text-white transition">
|
||||||
|
<Eye size={15} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button className="flex h-8 w-8 items-center justify-center rounded-md border border-slate-700/60 text-slate-400 hover:bg-slate-800 hover:text-white transition">
|
||||||
|
<Settings size={15} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button className="flex h-8 w-8 items-center justify-center rounded-md border border-slate-700/60 text-slate-400 hover:bg-slate-800 hover:text-white transition">
|
||||||
|
<GitBranch size={15} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button className="flex h-8 w-8 items-center justify-center rounded-md border border-slate-700/60 text-slate-400 hover:bg-slate-800 hover:text-white transition">
|
||||||
|
<Pencil size={15} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const organizationRows = [
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-500/15 font-semibold text-emerald-400">
|
||||||
|
AO
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">Apple One</div>
|
||||||
|
<div className="text-slate-400">ops@appleone.com</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
TNT-1001
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">Dhananjay T.</div>
|
||||||
|
<div className="text-sky-300">+91 98200 10001</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-mono">Asia/Kolkata</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-500/15 font-semibold text-blue-400">
|
||||||
|
NP
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">Novotel Pune</div>
|
||||||
|
<div className="text-slate-400">fm@novotelpune.com</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
TNT-1002
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">Site FM Lead</div>
|
||||||
|
<div className="text-sky-300">+91 98220 20002</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-mono">Asia/Kolkata</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-500/15 font-semibold text-blue-400">
|
||||||
|
SC
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">SRM Campus</div>
|
||||||
|
<div className="text-slate-400">estates@srm.edu</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
TNT-1003
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">Estates Office</div>
|
||||||
|
<div className="text-sky-300">+91 98430 30003</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-mono">Asia/Kolkata</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-500/15 font-semibold text-blue-400">
|
||||||
|
L1
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">Local 1</div>
|
||||||
|
<div className="text-slate-400">admin@local1.com</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
TNT-1004
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-slate-100">Local Admin</div>
|
||||||
|
<div className="text-sky-300">+91 98000 40004</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-mono">Asia/Kolkata</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="Active"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<ActionButtons />,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function TenantsPortal() {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans ">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono">
|
||||||
|
<span className="text-teal-400">PLATFORM</span>
|
||||||
|
<span className="text-slate-500">•</span>
|
||||||
|
<span className="text-slate-500">TENANTS</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-bold text-white tracking-tight mt-1">
|
||||||
|
Tenants
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-xl leading-relaxed">
|
||||||
|
Every organization provisioned on the platform. Manage onboarding, feature access, branding, and status.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- HEADER BUTTON --- */}
|
||||||
|
<div className="flex items-center gap-3 self-start md:mt-2">
|
||||||
|
<Button className="bg-emerald-400 hover:bg-emerald-300 text-slate-950 font-semibold text-xs px-4 py-2.5 rounded-lg flex items-center gap-1.5 transition-colors shadow-lg shadow-emerald-500/10">
|
||||||
|
<Plus className="w-4 h-4 text-slate-950 stroke-[3]" />
|
||||||
|
<span>Add tenant</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* --- TABLE CONTAINER WITH UPPER BAR --- */}
|
||||||
|
<div className="bg-slate-900/60 border border-slate-800 p-4 space-y-4">
|
||||||
|
{/* Table Upper UI (Search Bar & Filter Dropdown) */}
|
||||||
|
<div className="flex flex-col sm:flex-row items-center gap-3">
|
||||||
|
{/* Search Bar Input */}
|
||||||
|
<div className="relative flex-1 w-full">
|
||||||
|
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
|
||||||
|
|
||||||
|
placeholder="Search by name, ID or contact..."
|
||||||
|
className="w-full pl-10 pr-4 py-2 border border-slate-800 rounded-lg text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:border-slate-700 transition"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* All Statuses Filter Select Dropdown */}
|
||||||
|
<div className="relative w-full sm:w-44">
|
||||||
|
<select className="w-full appearance-none bg-slate-950/80 border border-slate-800 rounded-lg px-3.5 py-2 pr-8 text-sm text-slate-300 font-medium focus:outline-none focus:border-slate-700 cursor-pointer">
|
||||||
|
<option value="all">All statuses</option>
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="inactive">Inactive</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- TABLE --- */}
|
||||||
|
<Table headers={organizationHeaders} rows={organizationRows} />
|
||||||
|
|
||||||
|
{/* Footer Record Count */}
|
||||||
|
<div className="text-xs text-slate-500 font-mono pt-1">
|
||||||
|
{organizationRows.length} records
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
305
components/dashboard/admin/Platform/users.tsx
Normal file
305
components/dashboard/admin/Platform/users.tsx
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Plus,
|
||||||
|
Eye,
|
||||||
|
KeyRound,
|
||||||
|
Search,
|
||||||
|
Sparkles
|
||||||
|
} from "lucide-react";
|
||||||
|
import Button from "@/components/common/button";
|
||||||
|
import Table from '@/components/common/dataTable';
|
||||||
|
import { StatusBadge } from '@/components/common/statusBadge';
|
||||||
|
|
||||||
|
export const userHeaders = [
|
||||||
|
"Name",
|
||||||
|
"Email",
|
||||||
|
"Phone",
|
||||||
|
"Tenants",
|
||||||
|
"MFA",
|
||||||
|
"User ID",
|
||||||
|
"Actions",
|
||||||
|
];
|
||||||
|
|
||||||
|
const UserActionButtons = () => (
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<Button className="flex h-9 w-9 items-center justify-center rounded-xl border border-slate-700/60 text-slate-400 hover:bg-slate-800 hover:text-white transition">
|
||||||
|
<Eye size={16} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button className="flex h-9 w-9 items-center justify-center rounded-xl border border-slate-700/60 text-slate-400 hover:bg-slate-800 hover:text-white transition">
|
||||||
|
<KeyRound size={16} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const userRows = [
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-amber-500/15 font-semibold text-amber-400">
|
||||||
|
DT
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
Dhananjay T.
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
dhananjay@kafm.io
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="font-mono text-sky-300">
|
||||||
|
+91 98200 10001
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
4 assigned
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="On"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
USR-5001
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<UserActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-cyan-500/15 font-semibold text-cyan-400">
|
||||||
|
MS
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
ME Supervisor
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
me.local@kafm.io
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="font-mono text-sky-300">
|
||||||
|
+91 98201 10010
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
1 assigned
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="On"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
USR-5002
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<UserActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-amber-500/15 font-semibold text-amber-400">
|
||||||
|
T
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
Technician
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
tech.local@kafm.io
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="font-mono text-sky-300">
|
||||||
|
+91 98202 10011
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
2 assigned
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="Off"
|
||||||
|
color="bg-slate-700/50 text-slate-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
USR-5003
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<UserActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-rose-500/15 font-semibold text-rose-400">
|
||||||
|
TC
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
Tenant Customer
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
tenant@kafm.io
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="font-mono text-sky-300">
|
||||||
|
+91 98203 10012
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
1 assigned
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="On"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
USR-5004
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<UserActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-violet-500/15 font-semibold text-violet-400">
|
||||||
|
HA
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
Helpdesk Agent
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
help@kafm.io
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="font-mono text-sky-300">
|
||||||
|
+91 98204 10013
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
2 assigned
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="On"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
USR-5005
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<UserActionButtons />,
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-orange-500/15 font-semibold text-orange-400">
|
||||||
|
EA
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
Energy Analyst
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<span className="font-semibold text-slate-100">
|
||||||
|
energy@kafm.io
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="font-mono text-sky-300">
|
||||||
|
+91 98430 30030
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
1 assigned
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<StatusBadge
|
||||||
|
text="On"
|
||||||
|
color="bg-emerald-500/15 text-emerald-400"
|
||||||
|
/>,
|
||||||
|
|
||||||
|
<span className="rounded-md border border-slate-700 bg-slate-900 px-3 py-1 font-mono text-slate-300">
|
||||||
|
USR-5006
|
||||||
|
</span>,
|
||||||
|
|
||||||
|
<UserActionButtons />,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function UserPortal() {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-15 text-slate-100 font-sans ">
|
||||||
|
{/* --- HEADER SECTION --- */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase font-mono">
|
||||||
|
<span className="text-teal-400">PLATFORM</span>
|
||||||
|
<span className="text-slate-500">•</span>
|
||||||
|
<span className="text-slate-500">USERS</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl font-bold text-white tracking-tight mt-1">
|
||||||
|
Users
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm font-medium text-slate-400 mt-1 max-w-2xl leading-relaxed">
|
||||||
|
All platform user accounts. Create credentials, assign tenants, and manage passwords. This is the single account store — Operations role access reads from here.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- HEADER BUTTON --- */}
|
||||||
|
<div className="flex items-center gap-3 self-start md:mt-2">
|
||||||
|
<Button className="bg-emerald-400 hover:bg-emerald-300 text-slate-950 font-semibold text-xs px-4 py-2.5 rounded-lg flex items-center gap-1.5 transition-colors shadow-lg shadow-emerald-500/10">
|
||||||
|
<Plus className="w-4 h-4 text-slate-950 stroke-[3]" />
|
||||||
|
<span>Add user</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- TABLE CONTAINER WITH UPPER BAR --- */}
|
||||||
|
<div className="bg-slate-900/60 border border-slate-800/80 rounded-xl p-4 space-y-4">
|
||||||
|
{/* Table Upper UI (Full-width Search Bar matching screenshot) */}
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="Search users by name, email or ID..."
|
||||||
|
className="w-full pl-10 pr-4 py-2.5 bg-slate-950/80 border border-slate-800 rounded-lg text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:border-slate-700 transition"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- TABLE --- */}
|
||||||
|
<Table headers={userHeaders} rows={userRows} />
|
||||||
|
|
||||||
|
{/* Footer Record Count */}
|
||||||
|
<div className="text-xs text-slate-500 font-mono pt-1">
|
||||||
|
{userRows.length} records
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
components/layouts/context/navContext.tsx
Normal file
31
components/layouts/context/navContext.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useState } from "react";
|
||||||
|
|
||||||
|
type Mode = "operations" | "admin";
|
||||||
|
|
||||||
|
interface NavContextType {
|
||||||
|
mode: Mode;
|
||||||
|
setMode: (mode: Mode) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Provide default values so useContext never returns null/undefined
|
||||||
|
const NavContext = createContext<NavContextType>({
|
||||||
|
mode: "operations",
|
||||||
|
setMode: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function NavProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [mode, setMode] = useState<Mode>("operations");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavContext.Provider value={{ mode, setMode }}>
|
||||||
|
{children}
|
||||||
|
</NavContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNav() {
|
||||||
|
const context = useContext(NavContext);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -1,83 +1,133 @@
|
|||||||
import { Search, HelpCircle, Globe, Sun, Bell, ChevronDown, MapPin } from 'lucide-react';
|
"use client";
|
||||||
|
|
||||||
|
import { Search, HelpCircle, Globe, Sun, Bell, ChevronDown, MapPin, ShieldCheck } from "lucide-react";
|
||||||
|
import { useNav } from "./context/navContext";
|
||||||
|
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
|
const { mode } = useNav();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
/*
|
|
||||||
- 'fixed top-0 right-0 left-64' anchors the navbar to the top of the screen,
|
|
||||||
leaving room exactly for your 64-width sidebar.
|
|
||||||
- 'bg-[#071321]' matches your master UI background.
|
|
||||||
- 'h-16' gives it a consistent height.
|
|
||||||
*/
|
|
||||||
<header className="fixed top-0 right-0 left-64 z-30 flex h-16 items-center justify-between border-b border-[#142538]/50 bg-[#071321] px-8">
|
<header className="fixed top-0 right-0 left-64 z-30 flex h-16 items-center justify-between border-b border-[#142538]/50 bg-[#071321] px-8">
|
||||||
|
{mode === "operations" ? (
|
||||||
|
/* ================= OPERATIONS NAVBAR ================= */
|
||||||
|
<>
|
||||||
|
{/* Left Group */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{/* Company Selector */}
|
||||||
|
<div className="flex items-center gap-2 rounded-full bg-[#0a1826] px-3 py-1.5 text-sm font-semibold text-[#10b981] border border-[#132338] cursor-pointer hover:bg-[#0f2338] transition whitespace-nowrap relative">
|
||||||
|
<span className="h-2 w-2 rounded-full bg-[#10b981] shrink-0"></span>
|
||||||
|
|
||||||
{/* Left Group */}
|
<select
|
||||||
<div className="flex items-center gap-2">
|
className=" bg-[#0a1826] outline-none cursor-pointer font-medium pr-5"
|
||||||
{/* Company Selector */}
|
|
||||||
<div className="flex items-center gap-2 rounded-full bg-[#0a1826] px-3 py-1.5 text-sm font-semibold text-[#10b981] border border-[#132338] cursor-pointer hover:bg-[#0f2338] transition whitespace-nowrap">
|
|
||||||
<span className="h-2 w-2 rounded-full bg-[#10b981] shrink-0"></span>
|
|
||||||
<span className="font-semibold">Apple One</span>
|
|
||||||
<ChevronDown className="h-3.5 w-3.5 text-[#10b981]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Location Selector */}
|
defaultValue="Apple One"
|
||||||
<div className="flex items-center gap-2 rounded-full bg-[#0a1826] px-4 py-2 text-sm font-medium text-[#cbd5e1] border border-[#132338] cursor-pointer hover:bg-[#0f2338] transition whitespace-nowrap">
|
>
|
||||||
<MapPin className="h-4 w-4 text-gray-500 shrink-0" />
|
<option>Apple One</option>
|
||||||
<span className="whitespace-nowrap">
|
<option>Novotel Pune</option>
|
||||||
Apple One — <span className="text-[#cbd5e1] font-semibold">HQ Tower</span>
|
<option>SRM Campus</option>
|
||||||
</span>
|
<option>Local 1</option>
|
||||||
<ChevronDown className="h-4 w-4 text-gray-500 shrink-0" />
|
</select>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Search Bar */}
|
<ChevronDown className="absolute right-3 h-3.5 w-3.5 pointer-events-none" />
|
||||||
<div className="relative w-60">
|
</div>
|
||||||
<Search className="absolute left-4 top-2.5 h-4 w-4 text-gray-500" />
|
{/* Location Selector */}
|
||||||
<input
|
<div className="flex items-center gap-2 rounded-full bg-[#0a1826] px-4 py-2 text-sm font-medium text-[#cbd5e1] border border-[#132338] cursor-pointer hover:bg-[#0f2338] transition whitespace-nowrap relative">
|
||||||
type="text"
|
<MapPin className="h-4 w-4 text-gray-500 shrink-0" />
|
||||||
placeholder="Search requests, equipment, met"
|
|
||||||
className="w-full rounded-xl bg-[#0a1826] pl-10 pr-4 py-2 text-sm text-white placeholder-gray-500 border border-[#132338] focus:outline-none focus:border-[#3de0c4]/50"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Utilities */}
|
<select
|
||||||
<div className="flex items-center gap-3">
|
className=" bg-[#0a1826] outline-none cursor-pointer font-medium pr-5"
|
||||||
{/* Tour */}
|
defaultValue="Apple One — HQ Tower"
|
||||||
<button className="flex items-center gap-2 rounded-xl border border-[#132338] bg-[#0a1826] px-4 py-2 text-sm font-semibold text-white transition hover:bg-[#14253d]">
|
>
|
||||||
<HelpCircle className="h-4 w-4 text-gray-400" />
|
<option>Apple One — HQ Tower</option>
|
||||||
<span>Tour</span>
|
<option>Apple One — Annexe</option>
|
||||||
</button>
|
</select>
|
||||||
|
|
||||||
{/* Language */}
|
<ChevronDown className="absolute right-4 h-4 w-4 text-gray-500 pointer-events-none" />
|
||||||
<div className="flex items-center gap-1.5 rounded-xl border border-[#132338] bg-[#0a1826] px-4 py-2 text-sm font-semibold text-[#cbd5e1] cursor-pointer hover:bg-[#14253d]">
|
</div>
|
||||||
<Globe className="h-4 w-4 text-gray-400" />
|
|
||||||
<span>English</span>
|
|
||||||
<ChevronDown className="h-4 w-4 text-gray-500" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Live Indicator */}
|
{/* Search Bar */}
|
||||||
<div className="flex items-center gap-2 px-1 text-sm font-bold text-[#10b981] tracking-wider">
|
<div className="relative w-60">
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-[#10b981]"></span>
|
<Search className="absolute left-4 top-2.5 h-4 w-4 text-gray-500" />
|
||||||
<span className="text-xs font-bold text-gray-400">LIVE</span>
|
<input
|
||||||
</div>
|
type="text"
|
||||||
|
placeholder="Search requests, equipment, met"
|
||||||
|
className="w-full rounded-xl bg-[#0a1826] pl-10 pr-4 py-2 text-sm text-white placeholder-gray-500 border border-[#132338] focus:outline-none focus:border-[#3de0c4]/50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Theme Toggle */}
|
{/* Right Utilities */}
|
||||||
<button className="flex h-9 w-9 items-center justify-center rounded-xl border border-[#132338] bg-[#0a1826] text-gray-400 hover:text-white transition">
|
<div className="flex items-center gap-3">
|
||||||
<Sun className="h-4 w-4" />
|
<button className="flex items-center gap-2 rounded-xl border border-[#132338] bg-[#0a1826] px-4 py-2 text-sm font-semibold text-white transition hover:bg-[#14253d]">
|
||||||
</button>
|
<HelpCircle className="h-4 w-4 text-gray-400" />
|
||||||
|
<span>Tour</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Notifications */}
|
<div className="flex items-center gap-1.5 rounded-xl border border-[#132338] bg-[#0a1826] px-4 py-2 text-sm font-semibold text-[#cbd5e1] cursor-pointer hover:bg-[#14253d]">
|
||||||
<div className="relative flex h-9 w-9 items-center justify-center rounded-xl border border-[#132338] bg-[#0a1826] cursor-pointer hover:bg-[#14253d] transition">
|
<Globe className="h-4 w-4 text-gray-400" />
|
||||||
<Bell className="h-4 w-4 text-gray-400" />
|
<span>English</span>
|
||||||
<span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-[#ef4444] text-[9px] font-bold text-white">
|
<ChevronDown className="h-4 w-4 text-gray-500" />
|
||||||
8
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Profile Avatar */}
|
<div className="flex items-center gap-2 px-1 text-sm font-bold text-[#10b981] tracking-wider">
|
||||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-[#1b3454] text-xs font-bold text-white cursor-pointer hover:opacity-90 transition">
|
<span className="h-1.5 w-1.5 rounded-full bg-[#10b981]"></span>
|
||||||
DH
|
<span className="text-xs font-bold text-gray-400">LIVE</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<button className="flex h-9 w-9 items-center justify-center rounded-xl border border-[#132338] bg-[#0a1826] text-gray-400 hover:text-white transition">
|
||||||
|
<Sun className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="relative flex h-9 w-9 items-center justify-center rounded-xl border border-[#132338] bg-[#0a1826] cursor-pointer hover:bg-[#14253d] transition">
|
||||||
|
<Bell className="h-4 w-4 text-gray-400" />
|
||||||
|
<span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-[#ef4444] text-[9px] font-bold text-white">
|
||||||
|
8
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-[#1b3454] text-xs font-bold text-white cursor-pointer hover:opacity-90 transition">
|
||||||
|
DH
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
/* ================= ADMIN NAVBAR ================= */
|
||||||
|
<>
|
||||||
|
{/* Left Breadcrumb */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-slate-300">
|
||||||
|
<ShieldCheck size={18} className="text-[#3de0c4]" />
|
||||||
|
<span className="font-bold text-white">Administration</span>
|
||||||
|
<span className="text-slate-500">›</span>
|
||||||
|
<span className="text-slate-400">Mobile Flows</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Utilities */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="relative w-64">
|
||||||
|
<Search className="absolute left-3 top-2.5 h-4 w-4 text-slate-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search tenants, users, sites..."
|
||||||
|
className="w-full rounded-lg bg-[#0a1826] pl-9 pr-4 py-1.5 text-xs text-white placeholder-slate-500 border border-[#132338] focus:outline-none focus:border-[#3de0c4]/50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1.5 rounded-full border border-teal-500/30 bg-teal-500/10 px-3 py-1 text-xs font-bold text-[#3de0c4]">
|
||||||
|
<span className="h-2 w-2 rounded-full bg-[#3de0c4]"></span>
|
||||||
|
<span>PRODUCTION • us-east-1</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="flex h-8 w-8 items-center justify-center rounded-lg border border-[#132338] bg-[#0a1826] text-slate-400 hover:text-white transition">
|
||||||
|
<Sun className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-[#1b3454] text-xs font-bold text-white">
|
||||||
|
SA
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,65 +1,78 @@
|
|||||||
import { SIDEBAR_ITEMS } from "@/constants/constant";
|
"use client";
|
||||||
|
|
||||||
|
import { OPERATIONS_SIDEBAR_ITEMS, ADMIN_SIDEBAR_ITEMS } from "@/constants/constant";
|
||||||
import SidebarItem from "./sideBarItem";
|
import SidebarItem from "./sideBarItem";
|
||||||
import {
|
import { ShieldCheck, Activity, ChevronDown, ChevronsLeft } from "lucide-react";
|
||||||
ShieldCheck,
|
|
||||||
Activity,
|
|
||||||
ChevronDown,
|
|
||||||
ChevronsLeft,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useSession, signOut } from "next-auth/react";
|
import { useSession, signOut } from "next-auth/react";
|
||||||
|
import { useNav } from "./context/navContext";
|
||||||
|
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
console.log("Session data ",session?.user?.name)
|
const { mode, setMode } = useNav();
|
||||||
console.log("Session data ",session?.user?.email)
|
|
||||||
|
const currentItems = mode === "operations" ? OPERATIONS_SIDEBAR_ITEMS : ADMIN_SIDEBAR_ITEMS;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="fixed bg-[#071321] text-white h-screen overflow-y-auto [scrollbar-color:#1b2a40_#071321] [scrollbar-width:thin]">
|
<aside className="fixed bg-[#071321] text-white h-screen w-64 overflow-y-auto [scrollbar-color:#1b2a40_#071321] [scrollbar-width:thin]">
|
||||||
<div className="flex items-center gap-2 px-2 pt-2 pb-3">
|
{/* App Logo */}
|
||||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg
|
<div className="flex items-center gap-2 px-4 pt-3 pb-3">
|
||||||
bg-[#3de0c4] font-bold text-[#05111d] text-sm">
|
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-[#3de0c4] font-bold text-[#05111d] text-sm">
|
||||||
K
|
K
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-bold tracking-tight text-white">
|
<span className="text-sm font-bold tracking-tight text-white">
|
||||||
KA<span className="text-[#3de0c4]">FM</span>
|
KA<span className="text-[#3de0c4]">FM</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className="border-[#142538]" />
|
<hr className="border-[#142538]" />
|
||||||
<div className="mx-4 my-4 flex rounded-xl
|
|
||||||
bg-[#091624] p-2 border border-[#142538]">
|
{/* Mode Switcher Buttons */}
|
||||||
<button className="flex flex-1
|
<div className="mx-4 my-4 flex rounded-xl bg-[#091624] p-1 border border-[#142538]">
|
||||||
items-center justify-center gap-1 rounded-lg bg-[#3de0c4] py-1.5 text-xs
|
<button
|
||||||
text-black font-bold shadow-sm">
|
onClick={() => setMode("operations")}
|
||||||
|
className={`flex flex-1 items-center justify-center gap-1 rounded-lg py-1.5 text-xs font-bold transition-all ${
|
||||||
|
mode === "operations"
|
||||||
|
? "bg-[#3de0c4] text-black shadow-sm"
|
||||||
|
: "text-slate-400 hover:text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Activity size={14} />
|
<Activity size={14} />
|
||||||
Operations
|
Operations
|
||||||
</button>
|
</button>
|
||||||
<button className="flex flex-1 items-center justify-center gap-1
|
|
||||||
rounded-lg py-1 text-xs text-slate-400 font-medium hover:text-white transition-colors">
|
<button
|
||||||
|
onClick={() => setMode("admin")}
|
||||||
|
className={`flex flex-1 items-center justify-center gap-1 rounded-lg py-1.5 text-xs font-bold transition-all ${
|
||||||
|
mode === "admin"
|
||||||
|
? "bg-[#3de0c4] text-black shadow-sm"
|
||||||
|
: "text-slate-400 hover:text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<ShieldCheck size={14} />
|
<ShieldCheck size={14} />
|
||||||
Admin
|
Admin
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="px-2 text-xs font-semibold uppercase
|
<p className="px-4 text-[10px] font-semibold uppercase tracking-wider text-slate-500">
|
||||||
tracking-wider text-slate-500">
|
{mode === "operations" ? "Tenant Runtime" : "System Platform"}
|
||||||
Tenant runtime
|
|
||||||
</p>
|
</p>
|
||||||
<div className="py-1">
|
|
||||||
{SIDEBAR_ITEMS.map((item) => (
|
|
||||||
<SidebarItem
|
|
||||||
key={item.title}
|
|
||||||
item={item}
|
|
||||||
|
|
||||||
/>
|
{/* Dynamic Navigation Items */}
|
||||||
|
<div className="py-1">
|
||||||
|
{currentItems.map((item) => (
|
||||||
|
<SidebarItem key={item.title} item={item} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 pb-2 pt-1
|
|
||||||
border-t border-[#0f1f30]">
|
<div className="px-3 pb-2 pt-1 border-t border-[#0f1f30]">
|
||||||
<button className="flex w-full items-center gap-2 rounded-xl py-2 px-3 text-xs text-slate-400 hover:bg-[#091624] hover:text-white transition-colors">
|
<button className="flex w-full items-center gap-2 rounded-xl py-2 px-3 text-xs text-slate-400 hover:bg-[#091624] hover:text-white transition-colors">
|
||||||
<ChevronsLeft size={16} />
|
<ChevronsLeft size={16} />
|
||||||
Minimise menu
|
Minimise menu
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Profile Card Section */}
|
{/* Profile Card */}
|
||||||
<div className="m-3 mt-0 rounded-xl border border-[#142538] bg-[#071321] p-2.5">
|
<div className="m-3 mt-0 rounded-xl border border-[#142538] bg-[#071321] p-2.5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex gap-2.5 items-center">
|
<div className="flex gap-2.5 items-center">
|
||||||
@@ -67,16 +80,19 @@ console.log("Session data ",session?.user?.email)
|
|||||||
DT
|
DT
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 leading-tight">
|
<div className="min-w-0 leading-tight">
|
||||||
<h4 className="font-semibold text-xs text-white truncate">{session?.user?.name}</h4>
|
<h4 className="font-semibold text-xs text-white truncate">{session?.user?.name || "User"}</h4>
|
||||||
<p className="text-[11px] text-slate-500 truncate">
|
<p className="text-[11px] text-slate-500 truncate">
|
||||||
{session?.user?.email}
|
{session?.user?.email || "user@example.com"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ChevronDown className="-rotate-90 text-slate-500 shrink-0" size={14}
|
<ChevronDown
|
||||||
onClick={() => signOut({ callbackUrl: "/login" })} />
|
className="-rotate-90 text-slate-500 shrink-0 cursor-pointer"
|
||||||
</div>
|
size={14}
|
||||||
|
onClick={() => signOut({ callbackUrl: "/login" })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
|
|
||||||
import { TableRowData } from '@/types/types';
|
|
||||||
import {
|
import {
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
Smile,
|
Smile,
|
||||||
@@ -36,11 +35,16 @@ import {
|
|||||||
Wallet,
|
Wallet,
|
||||||
Radar,
|
Radar,
|
||||||
Cog,
|
Cog,
|
||||||
|
Smartphone,
|
||||||
|
Users,
|
||||||
|
Database,
|
||||||
|
Globe,
|
||||||
|
LayoutDashboard,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
//Side bar Drop Down menu
|
//Side bar Drop Down menu
|
||||||
|
|
||||||
export const SIDEBAR_ITEMS = [
|
export const OPERATIONS_SIDEBAR_ITEMS = [
|
||||||
{
|
{
|
||||||
title: "Overview",
|
title: "Overview",
|
||||||
icon: LayoutGrid,
|
icon: LayoutGrid,
|
||||||
@@ -130,6 +134,31 @@ export const SIDEBAR_ITEMS = [
|
|||||||
{ title: "Settings", href: "/configure/settings", icon: Sun },],
|
{ title: "Settings", href: "/configure/settings", icon: Sun },],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
export const ADMIN_SIDEBAR_ITEMS = [
|
||||||
|
{
|
||||||
|
title: "Platform",
|
||||||
|
icon: Building2,
|
||||||
|
children: [
|
||||||
|
{ title: "Overview", href: "/admin/overview", icon: LayoutGrid },
|
||||||
|
{ title: "Tenants", href: "/admin/tenants", icon: Building2 },
|
||||||
|
{ title: "Users", href: "/admin/users", icon: Users },
|
||||||
|
{ title: "Sites", href: "/admin/sites", icon: MapPin },
|
||||||
|
{ title: "Security", href: "/admin/security", icon: Shield },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Configuration",
|
||||||
|
icon: LayoutGrid,
|
||||||
|
children: [
|
||||||
|
{ title: "Modules", href: "/admin/configuration/modules", icon: LayoutGrid },
|
||||||
|
{ title: "Language", href: "/admin/configuration/language", icon: Globe },
|
||||||
|
{ title: "Dynamic Dashboard", href: "/admin/configuration/dynamic-dashboard", icon: LayoutDashboard },
|
||||||
|
{ title: "Mobile Flows", href: "/admin/configuration/mobile-flows", icon: Smartphone },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Overview dashboard data
|
//Overview dashboard data
|
||||||
|
|
||||||
@@ -147,6 +176,8 @@ export const chartData = [
|
|||||||
{ month: 'N', height: 'h-26' },
|
{ month: 'N', height: 'h-26' },
|
||||||
{ month: 'D', height: 'h-24' },
|
{ month: 'D', height: 'h-24' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export const energyData = [
|
export const energyData = [
|
||||||
{ month: "Jan", height: 'h-20' },
|
{ month: "Jan", height: 'h-20' },
|
||||||
{ month: "Feb", height: 'h-12' },
|
{ month: "Feb", height: 'h-12' },
|
||||||
@@ -197,8 +228,15 @@ export const equipmentTabs: string[] = [
|
|||||||
'AMC Schedule',
|
'AMC Schedule',
|
||||||
'Maintenance Calendar'
|
'Maintenance Calendar'
|
||||||
];
|
];
|
||||||
export const helpdeskTtabs: string[] = ['Location', 'Technical',
|
|
||||||
'Missed', 'Departments', 'HSQE'];
|
|
||||||
|
|
||||||
|
export const helpdeskTtabs: string[] =
|
||||||
|
['Location',
|
||||||
|
'Technical',
|
||||||
|
'Missed',
|
||||||
|
'Departments',
|
||||||
|
'HSQE'];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user