Operations dashboard screens are added
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client"// app/dashboard/layout.tsx
|
||||
import Sidebar from "@/components/dashboard/sidebar";
|
||||
import Navbar from "@/components/dashboard/navbar";
|
||||
import Sidebar from "@/utils/sidebar";
|
||||
import Navbar from "@/utils/navbar";
|
||||
import { Sparkles } from "lucide-react";
|
||||
|
||||
export default function DashboardLayout({
|
||||
@@ -14,7 +14,7 @@ export default function DashboardLayout({
|
||||
<Sidebar />
|
||||
|
||||
<div className="flex-1 flex flex-col min-h-screen">
|
||||
{/* Shared Navbar */}
|
||||
{/*Common Navbar for every webpage */}
|
||||
<Navbar />
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function DashboardLayout({
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Shared Ask AI Floating Button */}
|
||||
{/* Ask AI Floating Button common */}
|
||||
<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
|
||||
font-bold text-[#05111d] transition shadow-lg shadow-[#17d4bd]/20">
|
||||
|
||||
14
app/dashboard/operations/equipment/page.tsx
Normal file
14
app/dashboard/operations/equipment/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import EquipmentPortal from "@/components/dashboard/equipment"
|
||||
export default function HelpDeskPage() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-8 mx-3">
|
||||
<EquipmentPortal/>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
14
app/dashboard/operations/helpDesk/page.tsx
Normal file
14
app/dashboard/operations/helpDesk/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import HelpDeskPortal from "@/components/dashboard/helpDesk"
|
||||
export default function HelpDeskPage() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-8 mx-3">
|
||||
<HelpDeskPortal/>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
0
app/dashboard/operations/inventory/page.tsx
Normal file
0
app/dashboard/operations/inventory/page.tsx
Normal file
14
app/dashboard/operations/request/page.tsx
Normal file
14
app/dashboard/operations/request/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import ServiceRequestsPortal from "@/components/dashboard/request"
|
||||
export default function ServiceRequestPage() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-8 mx-3">
|
||||
<ServiceRequestsPortal/>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export default function OverViewDashboardPage() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-6">
|
||||
<div className="p-8 mx-3">
|
||||
<BuildingOwnerPortal/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function OverViewDashboardPage() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-6">
|
||||
<div className="p-8 mx-3">
|
||||
<OperationsDashboard />
|
||||
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function EngagementDashboard() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-6">
|
||||
<div className="p-8 mx-3">
|
||||
<EngagementPage/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function EngagementDashboard() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-6">
|
||||
<div className="p-8 mx-3">
|
||||
<PropertyManagerPortal/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function EngagementDashboard() {
|
||||
return (
|
||||
|
||||
|
||||
<div className="p-6">
|
||||
<div className="p-8 mx-3 ">
|
||||
<ServiceEngineerPortal/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/route"; // Changed fro
|
||||
export default async function DashboardBasePage() {
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
// if user is not aunthenticated then its redirects to the
|
||||
// login page else redirect to dashboard page
|
||||
if (!session) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
@@ -5,12 +5,7 @@
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { AuthLayoutProps } from "@/constants/types";
|
||||
import LeftPanel from "./leftpanel";
|
||||
|
||||
interface AuthLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function AuthLayout({ children }: AuthLayoutProps) {
|
||||
return (
|
||||
<main className="min-h-screen grid lg:grid-cols-2 bg-[#081321]">
|
||||
{/* Left Panel of the KAFM page */}
|
||||
<LeftPanel />
|
||||
|
||||
|
||||
{/* Login Page Right Side */}
|
||||
<div className="flex items-center justify-center p-8">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,11 @@ import Button from "@/utils/button";
|
||||
|
||||
|
||||
import {Props} from "@/constants/types"
|
||||
|
||||
|
||||
export default function ForgotPassword({ onBack }: Props) {
|
||||
return (
|
||||
// Forget Password Screen
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={onBack}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function LeftPanel() {
|
||||
Unified Facility Platform
|
||||
</p>
|
||||
|
||||
{/* Punchy Catchphrase */}
|
||||
{/*Text*/}
|
||||
<h1 className="max-w-lg text-4xl font-bold tracking-tight text-white leading-[1]">
|
||||
One platform.
|
||||
<br />
|
||||
@@ -67,7 +67,7 @@ export default function LeftPanel() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Global Statistics Footer */}
|
||||
{/*StatCard is used */}
|
||||
<div className=" flex gap-2 mt-10 text-slate-300 ">
|
||||
<Stat number="4" label="TENANTS" />
|
||||
<Stat number="8" label="MODULES" />
|
||||
|
||||
@@ -8,7 +8,8 @@ import { signIn } from "next-auth/react";
|
||||
|
||||
|
||||
export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);// For Visibility of Password
|
||||
return (
|
||||
|
||||
<div className="w-full max-w-md space-y-3">
|
||||
@@ -32,6 +33,7 @@ export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
|
||||
/>
|
||||
|
||||
<div className="relative">
|
||||
// Password Input
|
||||
<FormInput
|
||||
label="PASSWORD"
|
||||
value=""
|
||||
@@ -69,7 +71,7 @@ export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Actions / Submit Stack */}
|
||||
{/* Action on Button */}
|
||||
<div className="space-y-4">
|
||||
<Button
|
||||
onClick={onSuccess}
|
||||
@@ -78,7 +80,7 @@ export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
|
||||
Sign in
|
||||
</Button>
|
||||
|
||||
{/* Custom Visual Divider */}
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-4 py-2">
|
||||
<div className="h-px flex-1 bg-slate-800" />
|
||||
<span className="text-xs uppercase tracking-wider text-slate-600 font-medium">or</span>
|
||||
@@ -95,7 +97,7 @@ export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Bottom Legal Info */}
|
||||
{/* Bottom Info */}
|
||||
<p className="text-center text-xs tracking-wide text-slate-500 font-mono">
|
||||
Protected by 2-factor auth • every sign-in is
|
||||
<br/> audit-logged
|
||||
|
||||
@@ -8,6 +8,7 @@ import {Props} from "@/constants/types"
|
||||
export default function VerifyCode({ onBack }: Props) {
|
||||
const [timer, setTimer] = useState(30);
|
||||
|
||||
//Timer Functionality Sets 30s to 0s
|
||||
useEffect(() => {
|
||||
if (timer === 0) return;
|
||||
|
||||
@@ -21,7 +22,7 @@ export default function VerifyCode({ onBack }: Props) {
|
||||
const handleResend = () => {
|
||||
if (timer > 0) return;
|
||||
|
||||
// Call your resend OTP API here
|
||||
// Call your resend OTP
|
||||
|
||||
setTimer(30);
|
||||
};
|
||||
@@ -49,6 +50,8 @@ export default function VerifyCode({ onBack }: Props) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
{/* OTP input box */}
|
||||
<OTPInput />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function BuildingOwnerPortal() {
|
||||
<div className="mt-20 font-sans antialiased">
|
||||
|
||||
{/* --- HEADER SECTION --- */}
|
||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-8">
|
||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest text-slate-500 uppercase font-mono">
|
||||
<span className="text-teal-400">Apple One — HQ Tower</span>
|
||||
@@ -33,7 +33,10 @@ export default function BuildingOwnerPortal() {
|
||||
</div>
|
||||
|
||||
{/* Action Button */}
|
||||
<Button className="flex items-center gap-2 px-4 py-2 bg-[#091624] hover:bg-[#11233a] border border-[#142538] rounded-xl text-xs font-semibold text-slate-200 transition-colors duration-200 shadow-sm self-start md:mt-6">
|
||||
<Button className="flex items-center
|
||||
gap-2 px-4 py-2 bg-[#091624] hover:bg-[#11233a] border border-[#142538]
|
||||
rounded-xl text-xs font-semibold text-slate-200 transition-colors
|
||||
duration-200 shadow-sm self-start md:mt-15">
|
||||
<FileSpreadsheet size={14} className="text-slate-400" />
|
||||
Portfolio report
|
||||
</Button>
|
||||
|
||||
@@ -18,14 +18,16 @@ import { CardHeader } from "@/utils/cardHeader";
|
||||
|
||||
export default function EngagementPage() {
|
||||
return (
|
||||
<div className=" text-[#93a3b8] font-sans antialiased ">
|
||||
<div className=" text-[#93a3b8] font-sans ">
|
||||
|
||||
<main className="mt-20">
|
||||
|
||||
{/* Page Header Section */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mt-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center
|
||||
justify-between gap-4 mt-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-teal-500">
|
||||
<div className="flex items-center gap-2 text-xs
|
||||
font-bold uppercase tracking-widest text-teal-500">
|
||||
<span>Apple One — HQ Tower</span>
|
||||
<span className="text-gray-600">·</span>
|
||||
<span className="text-gray-400">Intelligence</span>
|
||||
@@ -41,7 +43,9 @@ export default function EngagementPage() {
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
className="flex items-center gap-2 rounded-xl border border-[#142538] bg-[#091624] px-4 py-2.5 text-xs font-bold text-white hover:bg-[#11233a] transition">
|
||||
className="flex items-center gap-2 rounded-xl border
|
||||
border-[#142538] bg-[#091624] px-4 py-4 text-xs font-bold text-white
|
||||
hover:bg-[#11233a] transition">
|
||||
<Smile size={15} className="text-slate-400" />
|
||||
Pulse survey
|
||||
</Button>
|
||||
|
||||
168
components/dashboard/equipment.tsx
Normal file
168
components/dashboard/equipment.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Target,
|
||||
AlertTriangle,
|
||||
Clock,
|
||||
ShieldCheck,
|
||||
Search,
|
||||
QrCode,
|
||||
Plus,
|
||||
Download,
|
||||
Upload
|
||||
} from 'lucide-react';
|
||||
import Button from "@/utils/button";
|
||||
import StatCard from "@/utils/dashStatCard";
|
||||
import LocationTable from '../../utils/table';
|
||||
|
||||
export default function EquipmentPortal() {
|
||||
const [activeTab, setActiveTab] = useState<string>('Site-Wise Equipment');
|
||||
|
||||
const tabs: string[] = [
|
||||
'Equipment Groups',
|
||||
'Site-Wise Equipment',
|
||||
'PPM Checklist',
|
||||
'AMC Checklist',
|
||||
'PPM Schedule',
|
||||
'AMC Schedule',
|
||||
'Maintenance Calendar'
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="my-15 text-slate-100 max-w-7xl mx-auto 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 text-slate-500 uppercase font-mono">
|
||||
<span className="text-teal-400">Apple One — HQ Tower</span>
|
||||
<span>•</span>
|
||||
<span>Operations</span>
|
||||
<span>•</span>
|
||||
<span>Equipment</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-white tracking-tight mt-2">
|
||||
Equipment & maintenance
|
||||
</h1>
|
||||
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed">
|
||||
Asset register, preventive (PPM) and contract (AMC) maintenance, scoped to the active tenant.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- NAVIGATION TABS --- */}
|
||||
<div className="flex items-center gap-8 border-b border-slate-800/80 mb-6 text-sm font-semibold overflow-x-auto no-scrollbar">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`pb-3 relative transition-colors whitespace-nowrap ${
|
||||
activeTab === tab
|
||||
? 'text-teal-400 border-b-2 border-teal-400 font-bold'
|
||||
: 'text-slate-400 hover:text-slate-200'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* --- STAT CARDS --- */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
{/* Total Equipment */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="Total equipment"
|
||||
value="8"
|
||||
icon={Target}
|
||||
badgeText="4 groups"
|
||||
badgeClassName="text-slate-400 lowercase font-mono"
|
||||
iconContainerClassName="bg-cyan-950/40 border-cyan-800/30 text-cyan-400"
|
||||
/>
|
||||
|
||||
{/* Critical Assets */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="Critical assets"
|
||||
value="3"
|
||||
icon={AlertTriangle}
|
||||
badgeText="priority"
|
||||
badgeClassName="text-rose-400 lowercase font-mono"
|
||||
iconContainerClassName="bg-rose-950/40 border-rose-800/30 text-rose-400"
|
||||
/>
|
||||
|
||||
{/* PPM Attention */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="PPM attention"
|
||||
value="5"
|
||||
icon={Clock}
|
||||
badgeText="due / overdue"
|
||||
badgeClassName="text-amber-400 lowercase font-mono"
|
||||
iconContainerClassName="bg-amber-950/40 border-amber-800/30 text-amber-400"
|
||||
/>
|
||||
|
||||
{/* On Plan */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="On plan"
|
||||
value="3"
|
||||
icon={ShieldCheck}
|
||||
badgeText="healthy"
|
||||
badgeClassName="text-emerald-400 lowercase font-mono"
|
||||
iconContainerClassName="bg-emerald-950/40 border-emerald-800/30 text-emerald-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* --- ROW 2: SEARCH, FILTERS & ACTION BUTTONS --- */}
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-4 mb-8">
|
||||
{/* Search & Select Filters */}
|
||||
<div className="flex flex-1 flex-wrap items-center gap-3 w-full md:w-auto">
|
||||
{/* Search Bar */}
|
||||
<div className="relative flex-1 min-w-[240px] max-w-md">
|
||||
<Search size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search code, name, block, provider..."
|
||||
className="w-full bg-[#0b1727] border border-slate-800 rounded-lg pl-9 pr-4 py-2 text-xs text-slate-200 placeholder-slate-500 focus:outline-none focus:border-teal-500/50 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Groups Dropdown */}
|
||||
<select className="bg-[#0b1727] border border-slate-800 rounded-lg px-4 py-2 text-xs font-semibold text-slate-200 cursor-pointer focus:outline-none focus:border-teal-500/50 transition-all">
|
||||
<option value="all">All groups</option>
|
||||
<option value="group 1">HVAC</option>
|
||||
|
||||
<option value="group 1">Electrical</option>
|
||||
<option value="group 3">Plumbing</option>
|
||||
<option value="group 4">Fire</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3 w-full md:w-auto justify-end">
|
||||
<Button className="flex items-center gap-2 bg-[#0b1727] hover:bg-slate-800/60 text-slate-200 border border-slate-800 rounded-lg px-3.5 py-2 text-xs font-semibold transition-colors">
|
||||
<QrCode size={15} />
|
||||
<span>All QR</span>
|
||||
</Button>
|
||||
|
||||
<Button className="flex items-center gap-2 bg-[#0b1727] hover:bg-slate-800/60 text-slate-200 border border-slate-800 rounded-lg px-3.5 py-2 text-xs font-semibold transition-colors">
|
||||
<Download size={15} />
|
||||
<span>Download</span>
|
||||
</Button>
|
||||
|
||||
<Button className="flex items-center gap-2 bg-[#0b1727] hover:bg-slate-800/60 text-slate-200 border border-slate-800 rounded-lg px-3.5 py-2 text-xs font-semibold transition-colors">
|
||||
<Upload size={15} />
|
||||
<span>Bulk upload</span>
|
||||
</Button>
|
||||
|
||||
<Button className="flex items-center gap-2 bg-teal-400 hover:bg-teal-300 text-slate-950 font-bold rounded-lg px-4 py-2 text-xs transition-colors">
|
||||
<Plus size={16} className="stroke-[3]" />
|
||||
<span>Add equipment</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LocationTable />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
155
components/dashboard/helpDesk.tsx
Normal file
155
components/dashboard/helpDesk.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Layers,
|
||||
ShieldCheck,
|
||||
CheckSquare,
|
||||
Clock,
|
||||
Search,
|
||||
QrCode,
|
||||
Plus,
|
||||
} from 'lucide-react';
|
||||
import Button from "@/utils/button";
|
||||
import StatCard from "@/utils/dashStatCard";
|
||||
import LocationTable from '../../utils/table';
|
||||
import NavigationTabs from '@/utils/tabs';
|
||||
|
||||
|
||||
export default function HelpDeskPortal() {
|
||||
const [activeTab, setActiveTab] = useState<string>('Location');
|
||||
|
||||
const tabs: string[] = ['Location', 'Technical', 'Missed', 'Departments', 'HSQE'];
|
||||
|
||||
return (
|
||||
<div className="my-15 text-slate-100 max-w-7xl mx-auto 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 text-slate-500 uppercase font-mono">
|
||||
<span className="text-teal-400">Apple One — HQ Tower</span>
|
||||
<span>•</span>
|
||||
<span>Operations</span>
|
||||
<span>•</span>
|
||||
<span>Help Desk</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-extrabold text-white tracking-tight mt-2">
|
||||
Help Desk & checklists
|
||||
</h1>
|
||||
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed">
|
||||
Scheduled location and equipment inspections, missed-checklist recovery, and HSQE incident management.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* Reusable Navigation Tabs */}
|
||||
<NavigationTabs
|
||||
tabs={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
className="mb-6"
|
||||
/>
|
||||
|
||||
{/* Rest of your page content */}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
{/* Total Checklists */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="Total checklists"
|
||||
value="10"
|
||||
icon={Layers}
|
||||
badgeText="Location set"
|
||||
badgeClassName="text-slate-500 lowercase font-mono"
|
||||
iconContainerClassName="bg-slate-800/60 border-slate-700/50 text-slate-300"
|
||||
/>
|
||||
|
||||
{/* Active */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="Active"
|
||||
value="7"
|
||||
icon={ShieldCheck}
|
||||
badgeText="of 10"
|
||||
badgeClassName="text-teal-400 font-mono font-medium"
|
||||
iconContainerClassName="bg-emerald-950/60 border-emerald-800/40 text-emerald-400"
|
||||
/>
|
||||
|
||||
{/* Approval Set */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="Approval set"
|
||||
value="5"
|
||||
icon={CheckSquare}
|
||||
badgeText="workflow"
|
||||
badgeClassName="text-slate-500 lowercase font-mono"
|
||||
iconContainerClassName="bg-amber-950/40 border-amber-800/30 text-amber-500"
|
||||
/>
|
||||
|
||||
{/* Avg TAT */}
|
||||
<StatCard
|
||||
variant="icon"
|
||||
title="Avg TAT"
|
||||
value="40m"
|
||||
icon={Clock}
|
||||
badgeText="target"
|
||||
badgeClassName="text-slate-500 lowercase font-mono"
|
||||
iconContainerClassName="bg-blue-950/50 border-blue-800/30 text-blue-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* --- ROW 2: SEARCH, FILTERS & ACTION BUTTONS --- */}
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-4 mb-8">
|
||||
{/* Search & Select Filters */}
|
||||
<div className="flex flex-1 flex-wrap items-center gap-3 w-full md:w-auto">
|
||||
{/* Search Bar */}
|
||||
<div className="relative flex-1 min-w-[240px] max-w-md">
|
||||
<Search size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search location, block, department..."
|
||||
className="w-full bg-[#0b1727] border border-slate-800 rounded-lg pl-9 pr-4 py-2 text-xs text-slate-200 placeholder-slate-500 focus:outline-none focus:border-teal-500/50 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Block Dropdown */}
|
||||
<select className="bg-[#081321] border border-slate-800/80 rounded-xl px-4 py-2 text-xs font-semibold text-slate-200 cursor-pointer focus:outline-none focus:ring-2 focus:ring-[#2dd4bf] focus:ring-offset-2 focus:ring-offset-[#081321] transition-all">
|
||||
<option value="all">All blocks</option>
|
||||
<option value="block 1">Block 1</option>
|
||||
<option value="block 2">Block 2</option>
|
||||
</select>
|
||||
|
||||
{/* Status Dropdown (Active State matching the screenshot) */}
|
||||
<select className="bg-[#081321] border border-transparent ring-2 ring-[#2dd4bf] ring-offset-2 ring-offset-[#081321] rounded-xl px-4 py-2 text-xs font-semibold text-slate-200 cursor-pointer focus:outline-none transition-all">
|
||||
<option value="all">All status</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">InActive</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3 w-full md:w-auto justify-end">
|
||||
<Button className="flex items-center gap-2 bg-[#0b1727] hover:bg-slate-800/60 text-slate-200 border border-slate-800 rounded-lg px-3.5 py-2 text-xs font-semibold transition-colors">
|
||||
<QrCode size={15} />
|
||||
<span>All QR</span>
|
||||
</Button>
|
||||
|
||||
<Button className="flex items-center gap-2 bg-teal-400 hover:bg-teal-300 text-slate-950 font-bold rounded-lg px-4 py-2 text-xs transition-colors">
|
||||
<Plus size={16} className="stroke-[3]" />
|
||||
<span>New Location Checklist</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<LocationTable/>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
0
components/dashboard/inventory.tsx
Normal file
0
components/dashboard/inventory.tsx
Normal file
@@ -16,11 +16,11 @@ import { Card } from "@/utils/cardWrapper";
|
||||
import { CardHeader } from "@/utils/cardHeader";
|
||||
export default function PropertyManagerPortal() {
|
||||
return (
|
||||
<div className=" text-slate-100 mt-15 font-sans antialiased
|
||||
<div className=" text-slate-100 mt-15
|
||||
">
|
||||
|
||||
{/* --- HEADER SECTION --- */}
|
||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-8">
|
||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest text-slate-500 uppercase font-mono">
|
||||
<span className="text-teal-400">Apple One — HQ Tower</span>
|
||||
@@ -29,7 +29,7 @@ export default function PropertyManagerPortal() {
|
||||
<span>•</span>
|
||||
<span>Property Manager</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-extrabold text-white tracking-tight mt-2">
|
||||
<h1 className="text-2xl font-bold text-white tracking-tight mt-2">
|
||||
Property manager portal
|
||||
</h1>
|
||||
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed">
|
||||
@@ -38,7 +38,7 @@ export default function PropertyManagerPortal() {
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3 self-start md:mt-6">
|
||||
<div className="flex items-center gap-3 self-start md:mt-15">
|
||||
<Button className="flex items-center gap-2 px-4 py-2 bg-[#091624] hover:bg-[#11233a] border border-[#142538] rounded-xl text-xs font-semibold text-slate-200 transition-colors duration-200 shadow-sm">
|
||||
<FileText size={14} className="text-slate-400" />
|
||||
Documents
|
||||
|
||||
96
components/dashboard/request.tsx
Normal file
96
components/dashboard/request.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useState } from 'react';
|
||||
import { Plus } from 'lucide-react';
|
||||
import Button from "@/utils/button";
|
||||
import LocationTable from '@/utils/table';
|
||||
import NavigationTabs from '@/utils/tabs';
|
||||
import { Reqeusttabs } from '@/constants/constant';
|
||||
|
||||
export default function ServiceRequestsPortal() {
|
||||
const [activeTab, setActiveTab] = useState<string>('TFM Requests');
|
||||
const [activeFilter, setActiveFilter] = useState<string>('All');
|
||||
|
||||
|
||||
|
||||
const statusFilters = [
|
||||
{ label: 'All', count: 18 },
|
||||
{ label: 'Registered', count: 4 },
|
||||
{ label: 'Assigned', count: 3 },
|
||||
{ label: 'Working', count: 4 },
|
||||
{ label: 'Completed', count: 3 },
|
||||
{ label: 'Overdue', count: 4 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="my-15 text-slate-100 max-w-7xl mx-auto 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 text-slate-500 uppercase font-mono">
|
||||
<span className="text-teal-400">Apple One — HQ Tower</span>
|
||||
<span>•</span>
|
||||
<span>Operations</span>
|
||||
<span>•</span>
|
||||
<span>Requests</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-white tracking-tight mt-2">
|
||||
Service requests
|
||||
</h1>
|
||||
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed">
|
||||
TFM helpdesk tickets, engineering breakdowns, and auto-generated recurring complaints — one request lifecycle.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- NAVIGATION TABS & TOP ACTION BUTTONS --- */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between border-b border-slate-800/80 mb-6 gap-4">
|
||||
{/* Navigation Tabs */}
|
||||
<NavigationTabs
|
||||
tabs={Reqeusttabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
className="mb-6"
|
||||
/>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3 pb-3 md:pb-0">
|
||||
<Button className="flex items-center gap-2 bg-[#0b1727] hover:bg-slate-800/60 text-slate-200 border border-slate-800 rounded-lg px-4 py-2 text-xs font-semibold transition-colors">
|
||||
<span>Export</span>
|
||||
</Button>
|
||||
|
||||
<Button className="flex items-center gap-2 bg-teal-400 hover:bg-teal-300 text-slate-950 font-bold rounded-lg px-4 py-2 text-xs transition-colors">
|
||||
<Plus size={16} className="stroke-[3]" />
|
||||
<span>New request</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- STATUS PILL FILTERS --- */}
|
||||
<div className="flex items-center gap-2.5 overflow-x-auto no-scrollbar py-1">
|
||||
{statusFilters.map((filter) => {
|
||||
const isActive = activeFilter === filter.label;
|
||||
return (
|
||||
<button
|
||||
key={filter.label}
|
||||
onClick={() => setActiveFilter(filter.label)}
|
||||
className={`flex items-center gap-2 px-3.5 py-1.5 rounded-full text-xs font-medium transition-all ${
|
||||
isActive
|
||||
? 'bg-teal-950/40 text-teal-400 border border-teal-500/50'
|
||||
: 'bg-[#0b1727] text-slate-400 border border-slate-800/80 hover:text-slate-200'
|
||||
}`}
|
||||
>
|
||||
<span>{filter.label}</span>
|
||||
<span
|
||||
className={`font-mono text-[11px] ${
|
||||
isActive ? 'text-teal-400 font-bold' : 'text-slate-400'
|
||||
}`}
|
||||
>
|
||||
{filter.count}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<LocationTable/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -16,10 +16,11 @@ import { CardHeader } from "@/utils/cardHeader";
|
||||
|
||||
export default function ServiceEngineerPortal() {
|
||||
return (
|
||||
<div className="text-slate-100 font-sans antialiased">
|
||||
<div className="my-15 text-slate-100 ">
|
||||
|
||||
{/* --- HEADER SECTION --- */}
|
||||
<div className="pt-20 flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-8">
|
||||
<div className=" flex flex-col
|
||||
md:flex-row md:items-start md:justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest text-slate-500 uppercase font-mono">
|
||||
<span className="text-teal-400">Apple One — HQ Tower</span>
|
||||
@@ -28,7 +29,7 @@ export default function ServiceEngineerPortal() {
|
||||
<span>•</span>
|
||||
<span>Service Engineer</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-extrabold text-white tracking-tight mt-2">
|
||||
<h1 className="text-2xl font-bold text-white tracking-tight mt-2">
|
||||
Service engineer portal
|
||||
</h1>
|
||||
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed">
|
||||
@@ -37,7 +38,7 @@ export default function ServiceEngineerPortal() {
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3 self-start md:mt-6">
|
||||
<div className="flex items-center gap-3 self-start md:mt-15">
|
||||
<Button className="flex items-center gap-2 px-4 py-2 bg-[#091624] hover:bg-[#11233a] border border-[#142538] rounded-xl text-xs font-semibold text-slate-200 transition-colors duration-200 shadow-sm">
|
||||
<FileText size={14} className="text-slate-400" />
|
||||
Scan Qr
|
||||
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
Cog,
|
||||
} from "lucide-react";
|
||||
|
||||
//Side bar Drop Down menu
|
||||
|
||||
export const SIDEBAR_ITEMS = [
|
||||
{
|
||||
title: "Overview",
|
||||
@@ -126,6 +128,8 @@ export const SIDEBAR_ITEMS = [
|
||||
},
|
||||
];
|
||||
|
||||
//Overview dashboard data
|
||||
|
||||
export const chartData = [
|
||||
{ month: 'J', height: 'h-24' },
|
||||
{ month: 'F', height: 'h-16' },
|
||||
@@ -179,3 +183,23 @@ export const TICKETS_DATA = [
|
||||
{ title: "More water dispensers", type: "Complaint", status: "CLOSED" },
|
||||
];
|
||||
|
||||
//Tabs used in Operations dashboards
|
||||
|
||||
export const equipmentTabs: string[] = [
|
||||
'Equipment Groups',
|
||||
'Site-Wise Equipment',
|
||||
'PPM Checklist',
|
||||
'AMC Checklist',
|
||||
'PPM Schedule',
|
||||
'AMC Schedule',
|
||||
'Maintenance Calendar'
|
||||
];
|
||||
export const helpdeskTtabs: string[] = ['Location', 'Technical',
|
||||
'Missed', 'Departments', 'HSQE'];
|
||||
|
||||
|
||||
export const Reqeusttabs: string[] = [
|
||||
'TFM Requests',
|
||||
'Engineering',
|
||||
'Schedule Complaints'
|
||||
];
|
||||
@@ -2,8 +2,14 @@ import { LucideProps } from "lucide-react";
|
||||
import { ComponentType } from "react";
|
||||
import { colorMap } from "./constant";
|
||||
|
||||
|
||||
|
||||
// Types and interfaces used in login Page
|
||||
export type Step = "login" | "verify" | "forgot";
|
||||
|
||||
export interface AuthLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
onBack?: () => void;
|
||||
@@ -11,6 +17,17 @@ export type Step = "login" | "verify" | "forgot";
|
||||
onForgotPassword?:()=>void;
|
||||
item?: any;
|
||||
}
|
||||
export interface FormProps {
|
||||
label: string;
|
||||
value?: string;
|
||||
type?: string;
|
||||
placeholder?:string
|
||||
}
|
||||
|
||||
|
||||
// Overview dashboard interface used
|
||||
|
||||
|
||||
interface Activity {
|
||||
time: string;
|
||||
text: string;
|
||||
@@ -54,6 +71,7 @@ export interface StatusProgressProps {
|
||||
barColor?: string;
|
||||
}
|
||||
|
||||
// For footer banner
|
||||
export interface InfoBannerProps {
|
||||
boldText?: string;
|
||||
normalText?: string;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
interface Props {
|
||||
label: string;
|
||||
value?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
import { FormProps } from "@/constants/types";
|
||||
export default function FormInput({
|
||||
label,
|
||||
value,
|
||||
type = "text",
|
||||
}: Props) {
|
||||
placeholder="string"
|
||||
}: FormProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="mb-2 block text-sm text-slate-400">
|
||||
@@ -18,7 +15,10 @@ export default function FormInput({
|
||||
<input
|
||||
type={type}
|
||||
defaultValue={value}
|
||||
placeholder={placeholder}
|
||||
|
||||
className="h-10 w-full rounded-xl border border-slate-700 text-white px-5"
|
||||
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function Sidebar() {
|
||||
console.log("Session data ",session?.user?.name)
|
||||
console.log("Session data ",session?.user?.email)
|
||||
return (
|
||||
<aside className="fixed bg-[#071321] text-white h-screen overflow-y-auto">
|
||||
<aside className="fixed bg-[#071321] text-white h-screen overflow-y-auto [scrollbar-color:#1b2a40_#071321] [scrollbar-width:thin]">
|
||||
<div className="flex items-center gap-2 px-2 pt-2 pb-3">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg
|
||||
bg-[#3de0c4] font-bold text-[#05111d] text-sm">
|
||||
226
utils/table.tsx
Normal file
226
utils/table.tsx
Normal file
@@ -0,0 +1,226 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Pencil,
|
||||
Menu,
|
||||
ShieldCheck,
|
||||
QrCode,
|
||||
Power,
|
||||
Check,
|
||||
Sparkles,
|
||||
} from 'lucide-react';
|
||||
|
||||
// Data model interface
|
||||
interface TableRowData {
|
||||
id: string;
|
||||
block: string;
|
||||
floor: string;
|
||||
zone: string;
|
||||
department: string;
|
||||
location: string;
|
||||
window: string;
|
||||
tat: string;
|
||||
sched: number;
|
||||
approval: boolean;
|
||||
status: 'ACTIVE' | 'INACTIVE';
|
||||
}
|
||||
|
||||
// Sample data extracted from your screenshot
|
||||
const mockData: TableRowData[] = [
|
||||
{ id: '1', block: 'Block 1', floor: 'G', zone: 'Z2', department: 'Electrical', location: 'Main lobby', window: '06:00–17:30', tat: '60m', sched: 8, approval: true, status: 'INACTIVE' },
|
||||
{ id: '2', block: 'Block 2', floor: '3', zone: 'Z2', department: 'Electrical', location: 'Main lobby', window: '08:00–23:59', tat: '30m', sched: 4, approval: false, status: 'ACTIVE' },
|
||||
{ id: '3', block: 'Block 2', floor: '3', zone: 'Z1', department: 'Inspection work', location: 'Cafeteria', window: '06:00–17:30', tat: '20m', sched: 8, approval: true, status: 'ACTIVE' },
|
||||
{ id: '4', block: 'Block 1', floor: 'G', zone: 'Z1', department: 'HVAC', location: 'Server room', window: '08:00–18:00', tat: '30m', sched: 4, approval: false, status: 'INACTIVE' },
|
||||
{ id: '5', block: 'Block 2', floor: '3', zone: 'N', department: 'Inspection work', location: 'Server room', window: '08:00–18:00', tat: '60m', sched: 8, approval: true, status: 'ACTIVE' },
|
||||
{ id: '6', block: 'Block 2', floor: 'G', zone: 'Z1', department: 'Electrical', location: 'Reception', window: '06:00–18:00', tat: '60m', sched: 6, approval: false, status: 'ACTIVE' },
|
||||
{ id: '7', block: 'Block 1', floor: 'G', zone: 'Z2', department: 'Electrical', location: 'Food court', window: '06:00–17:30', tat: '60m', sched: 8, approval: true, status: 'ACTIVE' },
|
||||
{ id: '8', block: 'Block 2', floor: '3', zone: 'Z2', department: 'Electrical', location: 'Cafeteria', window: '08:00–23:59', tat: '30m', sched: 4, approval: false, status: 'ACTIVE' },
|
||||
{ id: '9', block: 'Block 2', floor: '3', zone: 'Z1', department: 'Inspection work', location: 'Gym', window: '06:00–17:30', tat: '20m', sched: 8, approval: true, status: 'INACTIVE' },
|
||||
{ id: '10', block: 'Block 1', floor: 'G', zone: 'Z1', department: 'HVAC', location: 'Lobby', window: '08:00–18:00', tat: '30m', sched: 4, approval: false, status: 'ACTIVE' },
|
||||
];
|
||||
|
||||
export default function LocationTable(){
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>(['1']); // Row 1 selected by default (teal indicator bar)
|
||||
|
||||
const toggleSelectAll = () => {
|
||||
if (selectedIds.length === mockData.length) {
|
||||
setSelectedIds([]);
|
||||
} else {
|
||||
setSelectedIds(mockData.map((row) => row.id));
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSelectRow = (id: string) => {
|
||||
setSelectedIds((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full bg-[#071321] text-slate-300 rounded-xl overflow-hidden border border-slate-800/80 shadow-2xl relative font-sans">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-xs border-collapse">
|
||||
{/* TABLE HEADER */}
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800/90 text-[11px] font-bold tracking-wider text-slate-500 uppercase font-mono">
|
||||
<th className="py-4 px-4 w-12 text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.length === mockData.length}
|
||||
onChange={toggleSelectAll}
|
||||
className="rounded border-slate-700 bg-slate-900/80 text-teal-400 focus:ring-0 focus:ring-offset-0 cursor-pointer accent-teal-400 h-3.5 w-3.5"
|
||||
/>
|
||||
</th>
|
||||
<th className="py-4 px-3 font-mono">BLOCK</th>
|
||||
<th className="py-4 px-3 font-mono">FLOOR</th>
|
||||
<th className="py-4 px-3 font-mono">ZONE</th>
|
||||
<th className="py-4 px-3 font-mono">DEPARTMENT</th>
|
||||
<th className="py-4 px-3 font-mono">LOCATION</th>
|
||||
<th className="py-4 px-3 font-mono">WINDOW</th>
|
||||
<th className="py-4 px-3 font-mono">TAT</th>
|
||||
<th className="py-4 px-3 font-mono">SCHED.</th>
|
||||
<th className="py-4 px-3 font-mono">APPROVAL</th>
|
||||
<th className="py-4 px-3 font-mono">STATUS</th>
|
||||
<th className="py-4 px-4 font-mono text-right pr-6">ACTIONS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* TABLE BODY */}
|
||||
<tbody className="divide-y divide-slate-800/50">
|
||||
{mockData.map((row) => {
|
||||
const isSelected = selectedIds.includes(row.id);
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={`group relative transition-colors ${
|
||||
isSelected
|
||||
? 'bg-[#0e2238]/60 hover:bg-[#122942]/80'
|
||||
: 'hover:bg-slate-800/30'
|
||||
}`}
|
||||
>
|
||||
{/* Left Selection Indicator Bar (for Row 1 / Selected Rows) */}
|
||||
<td className="py-3 px-4 text-center relative">
|
||||
{isSelected && (
|
||||
<span className="absolute left-0 top-0 bottom-0 w-[3px] bg-teal-400 rounded-r" />
|
||||
)}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => toggleSelectRow(row.id)}
|
||||
className="rounded border-slate-700 bg-slate-900/80 text-teal-400 focus:ring-0 focus:ring-offset-0 cursor-pointer accent-teal-400 h-3.5 w-3.5"
|
||||
/>
|
||||
</td>
|
||||
|
||||
{/* Block */}
|
||||
<td className="py-3 px-3 font-semibold text-slate-100 whitespace-nowrap">
|
||||
{row.block}
|
||||
</td>
|
||||
|
||||
{/* Floor */}
|
||||
<td className="py-3 px-3 font-medium text-slate-200">
|
||||
{row.floor}
|
||||
</td>
|
||||
|
||||
{/* Zone */}
|
||||
<td className="py-3 px-3 font-medium text-slate-300">
|
||||
{row.zone}
|
||||
</td>
|
||||
|
||||
{/* Department */}
|
||||
<td className="py-3 px-3 font-semibold text-slate-200 whitespace-nowrap">
|
||||
{row.department}
|
||||
</td>
|
||||
|
||||
{/* Location */}
|
||||
<td className="py-3 px-3 font-extrabold text-white whitespace-nowrap">
|
||||
{row.location}
|
||||
</td>
|
||||
|
||||
{/* Window */}
|
||||
<td className="py-3 px-3 font-mono text-slate-400 text-[11px] whitespace-nowrap">
|
||||
{row.window}
|
||||
</td>
|
||||
|
||||
{/* TAT */}
|
||||
<td className="py-3 px-3 font-mono text-slate-200 font-medium">
|
||||
{row.tat}
|
||||
</td>
|
||||
|
||||
{/* Sched */}
|
||||
<td className="py-3 px-3 font-mono text-slate-200 font-medium">
|
||||
{row.sched}
|
||||
</td>
|
||||
|
||||
{/* Approval */}
|
||||
<td className="py-3 px-3 whitespace-nowrap">
|
||||
{row.approval ? (
|
||||
<span className="inline-flex items-center gap-1.5 text-emerald-400 font-semibold">
|
||||
<Check size={14} className="stroke-[3]" />
|
||||
<span>Yes</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-500 font-mono">——</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
{/* Status Badge */}
|
||||
<td className="py-3 px-3 whitespace-nowrap">
|
||||
{row.status === 'ACTIVE' ? (
|
||||
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-bold font-mono tracking-wider bg-emerald-950/80 border border-emerald-800/40 text-emerald-400">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400"></span>
|
||||
ACTIVE
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-bold font-mono tracking-wider bg-slate-800/80 border border-slate-700/50 text-slate-400">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-slate-400"></span>
|
||||
INACTIVE
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
{/* Action Icon Buttons */}
|
||||
<td className="py-3 px-4 text-right whitespace-nowrap pr-6">
|
||||
<div className="inline-flex items-center gap-1.5">
|
||||
<button
|
||||
title="Edit"
|
||||
className="p-1.5 rounded-md bg-[#0b1b2d] hover:bg-slate-700/60 border border-slate-800 text-slate-400 hover:text-slate-200 transition-colors"
|
||||
>
|
||||
<Pencil size={13} />
|
||||
</button>
|
||||
<button
|
||||
title="Options"
|
||||
className="p-1.5 rounded-md bg-[#0b1b2d] hover:bg-slate-700/60 border border-slate-800 text-slate-400 hover:text-slate-200 transition-colors"
|
||||
>
|
||||
<Menu size={13} />
|
||||
</button>
|
||||
<button
|
||||
title="Security"
|
||||
className="p-1.5 rounded-md bg-[#0b1b2d] hover:bg-slate-700/60 border border-slate-800 text-slate-400 hover:text-slate-200 transition-colors"
|
||||
>
|
||||
<ShieldCheck size={13} />
|
||||
</button>
|
||||
<button
|
||||
title="QR Code"
|
||||
className="p-1.5 rounded-md bg-[#0b1b2d] hover:bg-slate-700/60 border border-slate-800 text-slate-400 hover:text-slate-200 transition-colors"
|
||||
>
|
||||
<QrCode size={13} />
|
||||
</button>
|
||||
<button
|
||||
title="Toggle Power"
|
||||
className="p-1.5 rounded-md bg-[#0b1b2d] hover:bg-slate-700/60 border border-slate-800 text-slate-400 hover:text-slate-200 transition-colors"
|
||||
>
|
||||
<Power size={13} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
utils/tabs.tsx
Normal file
36
utils/tabs.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
// @/utils/NavigationTabs.tsx
|
||||
import React from 'react';
|
||||
|
||||
interface NavigationTabsProps {
|
||||
tabs: string[];
|
||||
activeTab: string;
|
||||
onTabChange: (tab: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function NavigationTabs({
|
||||
tabs,
|
||||
activeTab,
|
||||
onTabChange,
|
||||
className = '',
|
||||
}: NavigationTabsProps) {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-8 border-b border-slate-800 text-sm font-semibold overflow-x-auto no-scrollbar ${className}`}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => onTabChange(tab)}
|
||||
className={`pb-3 relative transition-colors whitespace-nowrap ${
|
||||
activeTab === tab
|
||||
? 'text-teal-400 border-b-2 border-teal-400 font-bold'
|
||||
: 'text-slate-400 hover:text-slate-200'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user