Operations dashboard screens are added

This commit is contained in:
2026-07-20 18:06:04 +05:30
parent a0426e8c8a
commit 0c9151b3a0
33 changed files with 826 additions and 47 deletions

View File

@@ -1,6 +1,6 @@
"use client"// app/dashboard/layout.tsx "use client"// app/dashboard/layout.tsx
import Sidebar from "@/components/dashboard/sidebar"; import Sidebar from "@/utils/sidebar";
import Navbar from "@/components/dashboard/navbar"; import Navbar from "@/utils/navbar";
import { Sparkles } from "lucide-react"; import { Sparkles } from "lucide-react";
export default function DashboardLayout({ export default function DashboardLayout({
@@ -14,7 +14,7 @@ export default function DashboardLayout({
<Sidebar /> <Sidebar />
<div className="flex-1 flex flex-col min-h-screen"> <div className="flex-1 flex flex-col min-h-screen">
{/* Shared Navbar */} {/*Common Navbar for every webpage */}
<Navbar /> <Navbar />
@@ -23,7 +23,7 @@ export default function DashboardLayout({
</main> </main>
</div> </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 <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">

View 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>
);
}

View 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>
);
}

View 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>
);
}

View File

@@ -9,7 +9,7 @@ export default function OverViewDashboardPage() {
return ( return (
<div className="p-6"> <div className="p-8 mx-3">
<BuildingOwnerPortal/> <BuildingOwnerPortal/>
</div> </div>

View File

@@ -9,7 +9,7 @@ export default function OverViewDashboardPage() {
return ( return (
<div className="p-6"> <div className="p-8 mx-3">
<OperationsDashboard /> <OperationsDashboard />
</div> </div>

View File

@@ -6,7 +6,7 @@ export default function EngagementDashboard() {
return ( return (
<div className="p-6"> <div className="p-8 mx-3">
<EngagementPage/> <EngagementPage/>
</div> </div>

View File

@@ -6,7 +6,7 @@ export default function EngagementDashboard() {
return ( return (
<div className="p-6"> <div className="p-8 mx-3">
<PropertyManagerPortal/> <PropertyManagerPortal/>
</div> </div>

View File

@@ -6,7 +6,7 @@ export default function EngagementDashboard() {
return ( return (
<div className="p-6"> <div className="p-8 mx-3 ">
<ServiceEngineerPortal/> <ServiceEngineerPortal/>
</div> </div>

View File

@@ -6,7 +6,8 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/route"; // Changed fro
export default async function DashboardBasePage() { export default async function DashboardBasePage() {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
// if user is not aunthenticated then its redirects to the
// login page else redirect to dashboard page
if (!session) { if (!session) {
redirect("/login"); redirect("/login");
} }

View File

@@ -5,12 +5,7 @@
--foreground: #171717; --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) { @media (prefers-color-scheme: dark) {
:root { :root {

View File

@@ -1,13 +1,15 @@
import { AuthLayoutProps } from "@/constants/types";
import LeftPanel from "./leftpanel"; import LeftPanel from "./leftpanel";
interface AuthLayoutProps {
children: React.ReactNode;
}
export default function AuthLayout({ children }: AuthLayoutProps) { export default function AuthLayout({ children }: AuthLayoutProps) {
return ( return (
<main className="min-h-screen grid lg:grid-cols-2 bg-[#081321]"> <main className="min-h-screen grid lg:grid-cols-2 bg-[#081321]">
{/* Left Panel of the KAFM page */}
<LeftPanel /> <LeftPanel />
{/* Login Page Right Side */}
<div className="flex items-center justify-center p-8"> <div className="flex items-center justify-center p-8">
{children} {children}
</div> </div>

View File

@@ -3,8 +3,11 @@ import Button from "@/utils/button";
import {Props} from "@/constants/types" import {Props} from "@/constants/types"
export default function ForgotPassword({ onBack }: Props) { export default function ForgotPassword({ onBack }: Props) {
return ( return (
// Forget Password Screen
<div className="space-y-4"> <div className="space-y-4">
<button <button
onClick={onBack} onClick={onBack}

View File

@@ -37,7 +37,7 @@ export default function LeftPanel() {
Unified Facility Platform Unified Facility Platform
</p> </p>
{/* Punchy Catchphrase */} {/*Text*/}
<h1 className="max-w-lg text-4xl font-bold tracking-tight text-white leading-[1]"> <h1 className="max-w-lg text-4xl font-bold tracking-tight text-white leading-[1]">
One platform. One platform.
<br /> <br />
@@ -67,7 +67,7 @@ export default function LeftPanel() {
</div> </div>
</div> </div>
{/* Global Statistics Footer */} {/*StatCard is used */}
<div className=" flex gap-2 mt-10 text-slate-300 "> <div className=" flex gap-2 mt-10 text-slate-300 ">
<Stat number="4" label="TENANTS" /> <Stat number="4" label="TENANTS" />
<Stat number="8" label="MODULES" /> <Stat number="8" label="MODULES" />

View File

@@ -8,7 +8,8 @@ import { signIn } from "next-auth/react";
export default function LoginForm({ onSuccess,onForgotPassword }: Props) { export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
const [showPassword, setShowPassword] = useState(false);
const [showPassword, setShowPassword] = useState(false);// For Visibility of Password
return ( return (
<div className="w-full max-w-md space-y-3"> <div className="w-full max-w-md space-y-3">
@@ -32,6 +33,7 @@ export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
/> />
<div className="relative"> <div className="relative">
// Password Input
<FormInput <FormInput
label="PASSWORD" label="PASSWORD"
value="" value=""
@@ -69,7 +71,7 @@ export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
</Button> </Button>
</div> </div>
{/* Actions / Submit Stack */} {/* Action on Button */}
<div className="space-y-4"> <div className="space-y-4">
<Button <Button
onClick={onSuccess} onClick={onSuccess}
@@ -78,7 +80,7 @@ export default function LoginForm({ onSuccess,onForgotPassword }: Props) {
Sign in Sign in
</Button> </Button>
{/* Custom Visual Divider */} {/* Divider */}
<div className="flex items-center gap-4 py-2"> <div className="flex items-center gap-4 py-2">
<div className="h-px flex-1 bg-slate-800" /> <div className="h-px flex-1 bg-slate-800" />
<span className="text-xs uppercase tracking-wider text-slate-600 font-medium">or</span> <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> </Button>
</div> </div>
{/* Bottom Legal Info */} {/* Bottom Info */}
<p className="text-center text-xs tracking-wide text-slate-500 font-mono"> <p className="text-center text-xs tracking-wide text-slate-500 font-mono">
Protected by 2-factor auth every sign-in is Protected by 2-factor auth every sign-in is
<br/> audit-logged <br/> audit-logged

View File

@@ -8,6 +8,7 @@ import {Props} from "@/constants/types"
export default function VerifyCode({ onBack }: Props) { export default function VerifyCode({ onBack }: Props) {
const [timer, setTimer] = useState(30); const [timer, setTimer] = useState(30);
//Timer Functionality Sets 30s to 0s
useEffect(() => { useEffect(() => {
if (timer === 0) return; if (timer === 0) return;
@@ -21,7 +22,7 @@ export default function VerifyCode({ onBack }: Props) {
const handleResend = () => { const handleResend = () => {
if (timer > 0) return; if (timer > 0) return;
// Call your resend OTP API here // Call your resend OTP
setTimer(30); setTimer(30);
}; };
@@ -49,6 +50,8 @@ export default function VerifyCode({ onBack }: Props) {
</p> </p>
</div> </div>
{/* OTP input box */}
<OTPInput /> <OTPInput />
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@@ -15,7 +15,7 @@ export default function BuildingOwnerPortal() {
<div className="mt-20 font-sans antialiased"> <div className="mt-20 font-sans antialiased">
{/* --- HEADER SECTION --- */} {/* --- 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>
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest text-slate-500 uppercase font-mono"> <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 className="text-teal-400">Apple One HQ Tower</span>
@@ -33,7 +33,10 @@ export default function BuildingOwnerPortal() {
</div> </div>
{/* Action Button */} {/* 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" /> <FileSpreadsheet size={14} className="text-slate-400" />
Portfolio report Portfolio report
</Button> </Button>

View File

@@ -18,14 +18,16 @@ import { CardHeader } from "@/utils/cardHeader";
export default function EngagementPage() { export default function EngagementPage() {
return ( return (
<div className=" text-[#93a3b8] font-sans antialiased "> <div className=" text-[#93a3b8] font-sans ">
<main className="mt-20"> <main className="mt-20">
{/* Page Header Section */} {/* 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>
<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>Apple One HQ Tower</span>
<span className="text-gray-600">·</span> <span className="text-gray-600">·</span>
<span className="text-gray-400">Intelligence</span> <span className="text-gray-400">Intelligence</span>
@@ -41,7 +43,9 @@ export default function EngagementPage() {
{/* Action Buttons */} {/* Action Buttons */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button <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" /> <Smile size={15} className="text-slate-400" />
Pulse survey Pulse survey
</Button> </Button>

View 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>
);
}

View 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>
);
}

View File

View File

@@ -16,11 +16,11 @@ import { Card } from "@/utils/cardWrapper";
import { CardHeader } from "@/utils/cardHeader"; import { CardHeader } from "@/utils/cardHeader";
export default function PropertyManagerPortal() { export default function PropertyManagerPortal() {
return ( return (
<div className=" text-slate-100 mt-15 font-sans antialiased <div className=" text-slate-100 mt-15
"> ">
{/* --- HEADER SECTION --- */} {/* --- 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>
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest text-slate-500 uppercase font-mono"> <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 className="text-teal-400">Apple One HQ Tower</span>
@@ -29,7 +29,7 @@ export default function PropertyManagerPortal() {
<span></span> <span></span>
<span>Property Manager</span> <span>Property Manager</span>
</div> </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 Property manager portal
</h1> </h1>
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed"> <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> </div>
{/* Action Buttons */} {/* 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"> <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" /> <FileText size={14} className="text-slate-400" />
Documents Documents

View 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>
);
}

View File

@@ -16,10 +16,11 @@ import { CardHeader } from "@/utils/cardHeader";
export default function ServiceEngineerPortal() { export default function ServiceEngineerPortal() {
return ( return (
<div className="text-slate-100 font-sans antialiased"> <div className="my-15 text-slate-100 ">
{/* --- HEADER SECTION --- */} {/* --- 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>
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest text-slate-500 uppercase font-mono"> <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 className="text-teal-400">Apple One HQ Tower</span>
@@ -28,7 +29,7 @@ export default function ServiceEngineerPortal() {
<span></span> <span></span>
<span>Service Engineer</span> <span>Service Engineer</span>
</div> </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 Service engineer portal
</h1> </h1>
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed"> <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> </div>
{/* Action Buttons */} {/* 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"> <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" /> <FileText size={14} className="text-slate-400" />
Scan Qr Scan Qr

View File

@@ -36,6 +36,8 @@ import {
Cog, Cog,
} from "lucide-react"; } from "lucide-react";
//Side bar Drop Down menu
export const SIDEBAR_ITEMS = [ export const SIDEBAR_ITEMS = [
{ {
title: "Overview", title: "Overview",
@@ -126,6 +128,8 @@ export const SIDEBAR_ITEMS = [
}, },
]; ];
//Overview dashboard data
export const chartData = [ export const chartData = [
{ month: 'J', height: 'h-24' }, { month: 'J', height: 'h-24' },
{ month: 'F', height: 'h-16' }, { month: 'F', height: 'h-16' },
@@ -179,3 +183,23 @@ export const TICKETS_DATA = [
{ title: "More water dispensers", type: "Complaint", status: "CLOSED" }, { 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'
];

View File

@@ -2,8 +2,14 @@ import { LucideProps } from "lucide-react";
import { ComponentType } from "react"; import { ComponentType } from "react";
import { colorMap } from "./constant"; import { colorMap } from "./constant";
// Types and interfaces used in login Page
export type Step = "login" | "verify" | "forgot"; export type Step = "login" | "verify" | "forgot";
export interface AuthLayoutProps {
children: React.ReactNode;
}
export interface Props { export interface Props {
onBack?: () => void; onBack?: () => void;
@@ -11,6 +17,17 @@ export type Step = "login" | "verify" | "forgot";
onForgotPassword?:()=>void; onForgotPassword?:()=>void;
item?: any; item?: any;
} }
export interface FormProps {
label: string;
value?: string;
type?: string;
placeholder?:string
}
// Overview dashboard interface used
interface Activity { interface Activity {
time: string; time: string;
text: string; text: string;
@@ -54,6 +71,7 @@ export interface StatusProgressProps {
barColor?: string; barColor?: string;
} }
// For footer banner
export interface InfoBannerProps { export interface InfoBannerProps {
boldText?: string; boldText?: string;
normalText?: string; normalText?: string;

View File

@@ -1,14 +1,11 @@
interface Props {
label: string;
value?: string;
type?: string;
}
import { FormProps } from "@/constants/types";
export default function FormInput({ export default function FormInput({
label, label,
value, value,
type = "text", type = "text",
}: Props) { placeholder="string"
}: FormProps) {
return ( return (
<div> <div>
<label className="mb-2 block text-sm text-slate-400"> <label className="mb-2 block text-sm text-slate-400">
@@ -18,7 +15,10 @@ export default function FormInput({
<input <input
type={type} type={type}
defaultValue={value} defaultValue={value}
placeholder={placeholder}
className="h-10 w-full rounded-xl border border-slate-700 text-white px-5" className="h-10 w-full rounded-xl border border-slate-700 text-white px-5"
/> />
</div> </div>
); );

View File

@@ -12,7 +12,7 @@ export default function Sidebar() {
console.log("Session data ",session?.user?.name) console.log("Session data ",session?.user?.name)
console.log("Session data ",session?.user?.email) console.log("Session data ",session?.user?.email)
return ( 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 items-center gap-2 px-2 pt-2 pb-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg <div className="flex h-9 w-9 items-center justify-center rounded-lg
bg-[#3de0c4] font-bold text-[#05111d] text-sm"> bg-[#3de0c4] font-bold text-[#05111d] text-sm">

226
utils/table.tsx Normal file
View 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:0017: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:0023: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:0017: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:0018: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:0018:00', tat: '60m', sched: 8, approval: true, status: 'ACTIVE' },
{ id: '6', block: 'Block 2', floor: 'G', zone: 'Z1', department: 'Electrical', location: 'Reception', window: '06:0018: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:0017:30', tat: '60m', sched: 8, approval: true, status: 'ACTIVE' },
{ id: '8', block: 'Block 2', floor: '3', zone: 'Z2', department: 'Electrical', location: 'Cafeteria', window: '08:0023: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:0017:30', tat: '20m', sched: 8, approval: true, status: 'INACTIVE' },
{ id: '10', block: 'Block 1', floor: 'G', zone: 'Z1', department: 'HVAC', location: 'Lobby', window: '08:0018: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
View 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>
);
}