From 89e93366e1e02c83db785a8355fce5ae9dbdc633 Mon Sep 17 00:00:00 2001 From: Shaily Mishra Date: Wed, 22 Jul 2026 18:34:47 +0530 Subject: [PATCH] All Screens in Operations are added --- app/dashboard/operations/energy/page.tsx | 16 + app/dashboard/operations/inventory/page.tsx | 14 + .../operations/vendorFieldService/page.tsx | 16 + app/dashboard/operations/workPermit/page.tsx | 16 + app/dashboard/workplace/meetingRoom/page.tsx | 0 .../workplace/visitorManagement/page.tsx | 0 app/dashboard/workplace/workspace/page.tsx | 0 components/dashboard/energy.tsx | 237 ++++++++ components/dashboard/equipment.tsx | 526 +++++++++++++----- components/dashboard/helpDesk.tsx | 2 +- components/dashboard/inventory.tsx | 456 +++++++++++++++ components/dashboard/request.tsx | 2 +- components/dashboard/vendorService.tsx | 331 +++++++++++ components/dashboard/workpermit.tsx | 352 ++++++++++++ constants/constant.ts | 13 +- constants/types.ts | 15 +- utils/{table.tsx => dataTable.tsx} | 200 ++++++- 17 files changed, 2050 insertions(+), 146 deletions(-) create mode 100644 app/dashboard/operations/energy/page.tsx create mode 100644 app/dashboard/operations/vendorFieldService/page.tsx create mode 100644 app/dashboard/operations/workPermit/page.tsx create mode 100644 app/dashboard/workplace/meetingRoom/page.tsx create mode 100644 app/dashboard/workplace/visitorManagement/page.tsx create mode 100644 app/dashboard/workplace/workspace/page.tsx create mode 100644 components/dashboard/energy.tsx create mode 100644 components/dashboard/vendorService.tsx create mode 100644 components/dashboard/workpermit.tsx rename utils/{table.tsx => dataTable.tsx} (97%) diff --git a/app/dashboard/operations/energy/page.tsx b/app/dashboard/operations/energy/page.tsx new file mode 100644 index 0000000..2c21636 --- /dev/null +++ b/app/dashboard/operations/energy/page.tsx @@ -0,0 +1,16 @@ +"use client" + +import EnergyPortal from "@/components/dashboard/energy"; + + +export default function InventoryPage() { + return ( + + +
+ + +
+ + ); +} \ No newline at end of file diff --git a/app/dashboard/operations/inventory/page.tsx b/app/dashboard/operations/inventory/page.tsx index e69de29..b3c5e7a 100644 --- a/app/dashboard/operations/inventory/page.tsx +++ b/app/dashboard/operations/inventory/page.tsx @@ -0,0 +1,14 @@ +"use client" + +import InventoryPortal from "@/components/dashboard/inventory"; +export default function InventoryPage() { + return ( + + +
+ + +
+ + ); +} \ No newline at end of file diff --git a/app/dashboard/operations/vendorFieldService/page.tsx b/app/dashboard/operations/vendorFieldService/page.tsx new file mode 100644 index 0000000..fdecb65 --- /dev/null +++ b/app/dashboard/operations/vendorFieldService/page.tsx @@ -0,0 +1,16 @@ +"use client" + +import VendorServicePortal from "@/components/dashboard/vendorService"; + + +export default function InventoryPage() { + return ( + + +
+ + +
+ + ); +} \ No newline at end of file diff --git a/app/dashboard/operations/workPermit/page.tsx b/app/dashboard/operations/workPermit/page.tsx new file mode 100644 index 0000000..cc2c821 --- /dev/null +++ b/app/dashboard/operations/workPermit/page.tsx @@ -0,0 +1,16 @@ +"use client" + +import WorkPermitPortal from "@/components/dashboard/workpermit"; + + +export default function InventoryPage() { + return ( + + +
+ + +
+ + ); +} \ No newline at end of file diff --git a/app/dashboard/workplace/meetingRoom/page.tsx b/app/dashboard/workplace/meetingRoom/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/dashboard/workplace/visitorManagement/page.tsx b/app/dashboard/workplace/visitorManagement/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/dashboard/workplace/workspace/page.tsx b/app/dashboard/workplace/workspace/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/dashboard/energy.tsx b/components/dashboard/energy.tsx new file mode 100644 index 0000000..c38f502 --- /dev/null +++ b/components/dashboard/energy.tsx @@ -0,0 +1,237 @@ +import { + Package, + AlertTriangle, + Activity, + FileText, + LayoutGrid, + Plus +} from "lucide-react"; +import Button from "@/utils/button"; +import StatCard from "@/utils/dashStatCard"; + + + +export default function EnergyPortal() { + const metersList = [ + { + meter: "MTR-1", + name: "Electricity main 1", + location: "Pump room", + type: "Electricity", + mf: "1", + }, + { + meter: "MTR-2", + name: "Water main 2", + location: "Main LT panel", + type: "Water", + mf: "10", + }, + { + meter: "MTR-3", + name: "Diesel main 3", + location: "DG room", + type: "Diesel", + mf: "100", + }, + { + meter: "MTR-4", + name: "Electricity main 4", + location: "Block riser", + type: "Electricity", + mf: "1", + }, + { + meter: "MTR-5", + name: "Water main 5", + location: "DG room", + type: "Water", + mf: "1", + }, + ]; + + const latestReadings = [ + { + meter: "MTR-1", + prev: "40.00", + current: "58.00", + cons: "18.0", + uom: "kWh", + }, + { + meter: "MTR-2", + prev: "49.00", + current: "69.00", + cons: "200.0", + uom: "kL", + }, + { + meter: "MTR-3", + prev: "58.00", + current: "80.00", + cons: "2200.0", + uom: "L", + }, + { + meter: "MTR-4", + prev: "67.00", + current: "91.00", + cons: "24.0", + uom: "kWh", + }, + { + meter: "MTR-5", + prev: "76.00", + current: "102.00", + cons: "26.0", + uom: "kL", + }, + ]; + + return ( +
+ {/* --- HEADER SECTION --- */} +
+
+
+ Apple One — HQ Tower + + Operations + + Energy +
+

+ Meters & readings +

+

+ Meter master with QR identification and reading capture. Consumption is reading + × multiplication factor. +

+
+ + {/* --- HEADER BUTTONS --- */} +
+ + +
+
+ + + + {/* --- TABLES GRID (SIDE-BY-SIDE) --- */} +
+ + {/* LEFT TABLE: METER MASTER */} +
+
+

Meter master

+ 5 meters +
+ +
+ + + + + + + + + + + + + {metersList.map((row, index) => { + return ( + + + + + + + + + ); + })} + +
MeterNameLocationTypeMF
+ {row.meter} + + {row.name} + + {row.location} + + {row.type} + + {row.mf} + +
+ + +
+
+
+
+ + {/* RIGHT TABLE: LATEST READINGS */} +
+
+

Latest readings

+ + tap a meter for history + +
+ +
+ + + + + + + + + + + + {latestReadings.map((row, index) => ( + + + + + + + + ))} + +
MeterPrevCurrentCons.UOM
+ {row.meter} + + {row.prev} + + {row.current} + + {row.cons} + + {row.uom} +
+
+
+ +
+ + +
+ ); +} \ No newline at end of file diff --git a/components/dashboard/equipment.tsx b/components/dashboard/equipment.tsx index 6db7d23..36b2d52 100644 --- a/components/dashboard/equipment.tsx +++ b/components/dashboard/equipment.tsx @@ -1,153 +1,397 @@ -import { useState } from 'react'; -import { - Target, - AlertTriangle, - Clock, - ShieldCheck, - Search, - QrCode, - Plus, - Download, - Upload +import { useState } from 'react'; + +import { + + Pencil, + + Menu, + + ShieldCheck, + + QrCode, + + Power, + + Check, + + Sparkles, + } from 'lucide-react'; -import Button from "@/utils/button"; -import StatCard from "@/utils/dashStatCard"; -import LocationTable from '../../utils/table'; -import NavigationTabs from '@/utils/tabs'; -import { equipmentTabs } from '@/constants/constant'; -export default function EquipmentPortal() { - const [activeTab, setActiveTab] = useState('Site-Wise Equipment'); +import { mockData } from '@/constants/constant'; + + + + + +export default function LocationTable(){ + + const [selectedIds, setSelectedIds] = useState(['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 ( -
- {/* --- HEADER SECTION --- */} -
-
-
- Apple One — HQ Tower - - Operations - - Equipment -
-

- Equipment & maintenance -

-

- Asset register, preventive (PPM) and contract (AMC) maintenance, scoped to the active tenant. -

-
+ +
+ +
+ + + + {/* TABLE HEADER */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* TABLE BODY */} + + + + {mockData.map((row) => { + + const isSelected = selectedIds.includes(row.id); + + + + return ( + + + + {/* Left Selection Indicator Bar (for Row 1 / Selected Rows) */} + + + + + + {/* Block */} + + + + + + {/* Floor */} + + + + + + {/* Zone */} + + + + + + {/* Department */} + + + + + + {/* Location */} + + + + + + {/* Window */} + + + + + + {/* TAT */} + + + + + + {/* Sched */} + + + + + + {/* Approval */} + + + + + + {/* Status Badge */} + + + + + + {/* Action Icon Buttons */} + + + + + + ); + + })} + + + +
+ + + + BLOCKFLOORZONEDEPARTMENTLOCATIONWINDOWTATSCHED.APPROVALSTATUSACTIONS
+ + {isSelected && ( + + + + )} + + 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" + + /> + + + + {row.block} + + + + {row.floor} + + + + {row.zone} + + + + {row.department} + + + + {row.location} + + + + {row.window} + + + + {row.tat} + + + + {row.sched} + + + + {row.approval ? ( + + + + + + Yes + + + + ) : ( + + —— + + )} + + + + {row.status === 'ACTIVE' ? ( + + + + + + ACTIVE + + + + ) : ( + + + + + + INACTIVE + + + + )} + + + +
+ + + + + + + + + + + +
+ +
+
- - {/* Navigation Tabs */} - - {/* --- STAT CARDS --- */} -
- {/* Total Equipment */} - - - {/* Critical Assets */} - - {/* PPM Attention */} - - - {/* On Plan */} - -
- {/* --- ROW 2: SEARCH, FILTERS & ACTION BUTTONS --- */} -
- {/* Search & Select Filters */} -
- {/* Search Bar */} -
- - -
+ - {/* Groups Dropdown */} - -
- - {/* Action Buttons */} -
- - - - - - - -
-
- -
+ ); -} \ No newline at end of file + +} \ No newline at end of file diff --git a/components/dashboard/helpDesk.tsx b/components/dashboard/helpDesk.tsx index 823c403..e4ebb9e 100644 --- a/components/dashboard/helpDesk.tsx +++ b/components/dashboard/helpDesk.tsx @@ -12,7 +12,7 @@ import { } from 'lucide-react'; import Button from "@/utils/button"; import StatCard from "@/utils/dashStatCard"; -import LocationTable from '../../utils/table'; +import LocationTable from '@/utils/dataTable'; import NavigationTabs from '@/utils/tabs'; import { helpdeskTtabs } from '@/constants/constant'; diff --git a/components/dashboard/inventory.tsx b/components/dashboard/inventory.tsx index e69de29..bb72548 100644 --- a/components/dashboard/inventory.tsx +++ b/components/dashboard/inventory.tsx @@ -0,0 +1,456 @@ +import { + Package, + AlertTriangle, + Activity, + FileText, + LayoutGrid, + Plus +} from "lucide-react"; +import Button from "@/utils/button"; +import StatCard from "@/utils/dashStatCard"; +import InfoBanner from "@/utils/infoBanner"; + + + + +export default function InventoryPortal() { + const stockItems = [ + { + item: "Cooling tower fan belt", + group: "HVAC", + store: "Block Store", + min: 5, + presentValue: 8, + unit: "Gallon", + status: "COMPLETED", + isHighlighted: false, + }, + { + item: "Air filter 24×24", + group: "HVAC", + store: "Block Store", + min: 5, + presentValue: 2, + unit: "Nos", + status: "OVERDUE", + isHighlighted: true, + }, + { + item: "MCB 32A", + group: "Electrical", + store: "Block Store", + min: 5, + presentValue: 8, + unit: "Nos", + status: "COMPLETED", + isHighlighted: false, + }, + { + item: "Contactor 40A", + group: "Electrical", + store: "Block Store", + min: 2, + presentValue: 2, + unit: "Nos", + status: "OVERDUE", + isHighlighted: false, + }, + { + item: "PVC pipe 1\"", + group: "Plumbing", + store: "Main Store", + min: 3, + presentValue: 0, + unit: "Mtr", + status: "OVERDUE", + isHighlighted: false, + }, + { + item: "Submersible pump seal", + group: "Plumbing", + store: "Main Store", + min: 3, + presentValue: 2, + unit: "Nos", + status: "OVERDUE", + isHighlighted: false, + }, + { + item: "Refrigerant R410a", + group: "HVAC", + store: "Block Store", + min: 5, + presentValue: 0, + unit: "Kg", + status: "OVERDUE", + isHighlighted: false, + }, + ]; + + const consumptionLedger = [ + { + txn: "TXN-5200", + item: "Cooling tower fan belt", + type: "Consume", + qty: "2 Gallon", + jobCard: "026/1009", + date: "Jun 20, 2026", + }, + { + txn: "TXN-5201", + item: "MCB 32A", + type: "Consume", + qty: "1 Nos", + jobCard: "026/1016", + date: "Jun 21, 2026", + }, + { + txn: "TXN-5202", + item: "Air filter 24×24", + type: "Receipt", + qty: "10 Nos", + jobCard: "—", + date: "Jun 22, 2026", + }, + { + txn: "TXN-5203", + item: "Submersible pump seal", + type: "Consume", + qty: "1 Nos", + jobCard: "026/1023", + date: "Jun 23, 2026", + }, + { + txn: "TXN-5204", + item: "Air filter 24×24", + type: "Consume", + qty: "3 Nos", + jobCard: "026/1030", + date: "Jun 24, 2026", + }, + { + txn: "TXN-5205", + item: "PVC pipe 1\"", + type: "Receipt", + qty: "20 Mtr", + jobCard: "—", + date: "Jun 25, 2026", + }, + { + txn: "TXN-5406", + item: "Cooling tower fan belt", + type: "Consume", + qty: "2 Gallon", + jobCard: "026/1009", + date: "Jun 7, 2026", + }, + { + txn: "TXN-5407", + item: "MCB 32A", + type: "Consume", + qty: "1 Nos", + jobCard: "026/1016", + date: "Jun 7, 2026", + }, + { + txn: "TXN-5200", + item: "Cooling tower fan belt", + type: "Consume", + qty: "2 Gallon", + jobCard: "026/1009", + date: "Jun 20, 2026", + }, + { + txn: "TXN-5201", + item: "MCB 32A", + type: "Consume", + qty: "1 Nos", + jobCard: "026/1016", + date: "Jun 21, 2026", + }, + { + txn: "TXN-5202", + item: "Air filter 24×24", + type: "Receipt", + qty: "10 Nos", + jobCard: "—", + date: "Jun 22, 2026", + }, + { + txn: "TXN-5203", + item: "Submersible pump seal", + type: "Consume", + qty: "1 Nos", + jobCard: "026/1023", + date: "Jun 23, 2026", + }, + { + txn: "TXN-5204", + item: "Air filter 24×24", + type: "Consume", + qty: "3 Nos", + jobCard: "026/1030", + date: "Jun 24, 2026", + }, + { + txn: "TXN-5205", + item: "PVC pipe 1\"", + type: "Receipt", + qty: "20 Mtr", + jobCard: "—", + date: "Jun 25, 2026", + }, + { + txn: "TXN-5406", + item: "Cooling tower fan belt", + type: "Consume", + qty: "2 Gallon", + jobCard: "026/1009", + date: "Jun 7, 2026", + }, + { + txn: "TXN-5407", + item: "MCB 32A", + type: "Consume", + qty: "1 Nos", + jobCard: "026/1016", + date: "Jun 7, 2026", + }, + ]; + + return ( +
+ {/* --- HEADER SECTION --- */} +
+
+
+ Apple One — HQ Tower + + Operations + + Inventory +
+

+ Inventory & stock +

+

+ Stock master with receipt / consume transactions. Consumption posts back to the + originating request job card; items at or below minimum are flagged for reorder. +

+
+ + {/* --- HEADER BUTTONS --- */} +
+ + + +
+
+ + {/* --- STAT CARDS --- */} +
+ {/* Stock items */} + + + {/* Below minimum */} + + + {/* Units consumed */} + + + {/* Posted to requests */} + +
+ + {/* --- TABLES GRID (SIDE-BY-SIDE) --- */} +
+ + {/* LEFT TABLE: STOCK MASTER */} +
+
+

Stock master

+ 7 items +
+ + {/* Corrected column layout and removed horizontal scrollbar to align with the right UI. */} +
+ + + + + + + + + + + + + {stockItems.map((row, index) => { + const isOverdue = row.status === "OVERDUE"; + return ( + + + + + + + + + ); + })} + +
ItemGroupStoreMinPresentStatus
+ {row.isHighlighted && ( +
+ )} + {row.item} +
+ {row.group} + + {row.store} + + {row.min} + + + {row.presentValue}{" "} + + + {row.unit} + + + {row.status === "COMPLETED" ? ( + + + COMPLETED + + ) : ( + + + OVERDUE + + )} +
+
+
+ + {/* RIGHT TABLE: CONSUMPTION LEDGER */} +
+
+

Consumption ledger

+ + posts back to job card + +
+ +
+ + + + + + + + + + + + + {consumptionLedger.map((row, index) => ( + + + + + + + + + ))} + +
TxnItemTypeQtyJob CardDate
+ {row.txn} + + {row.item} + + {row.type === "Consume" ? ( + + + Consume + + ) : ( + + + Receipt + + )} + + {row.qty} + + {row.jobCard} + + {row.date} +
+
+
+ +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/components/dashboard/request.tsx b/components/dashboard/request.tsx index 284f227..26e426d 100644 --- a/components/dashboard/request.tsx +++ b/components/dashboard/request.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Plus } from 'lucide-react'; import Button from "@/utils/button"; -import LocationTable from '@/utils/table'; +import LocationTable from '@/utils/dataTable'; import NavigationTabs from '@/utils/tabs'; import { Reqeusttabs,statusFilters } from '@/constants/constant'; import FilterTabs from '@/utils/roundedFilters'; diff --git a/components/dashboard/vendorService.tsx b/components/dashboard/vendorService.tsx new file mode 100644 index 0000000..3fe9087 --- /dev/null +++ b/components/dashboard/vendorService.tsx @@ -0,0 +1,331 @@ +import { useState } from 'react'; +import { + Star, + ShieldCheck, + Activity, + Clock, + BarChart2, + Plus +} from "lucide-react"; +import Button from "@/utils/button"; +import StatCard from "@/utils/dashStatCard"; +import InfoBanner from "@/utils/infoBanner"; + +export default function VendorServicePortal() { + const vendorData = [ + { + name: "Voltas Ltd", + id: "CTR-700", + scope: "HVAC AMC & breakdown", + rating: "4.6", + sla: "95%", + slaColor: "text-emerald-400", + response: "4.0h", + openJobs: "2", + }, + { + name: "Blue Star", + id: "CTR-701", + scope: "Chiller & VRV service", + rating: "4.1", + sla: "88%", + slaColor: "text-amber-400", + response: "1.2h", + openJobs: "2", + }, + { + name: "Sterling FM", + id: "CTR-702", + scope: "Integrated FM / soft services", + rating: "3.4", + sla: "94%", + slaColor: "text-emerald-400", + response: "2.4h", + openJobs: "4", + }, + { + name: "OTIS Elevators", + id: "CTR-704", + scope: "Elevator AMC", + rating: "4.8", + sla: "95%", + slaColor: "text-emerald-400", + response: "2.8h", + openJobs: "—", + }, + { + name: "Sodexo", + id: "CTR-705", + scope: "Soft services & cafeteria", + rating: "4.3", + sla: "90%", + slaColor: "text-amber-400", + response: "2.8h", + openJobs: "1", + }, + { + name: "In-house team", + id: "—", + scope: "Day-to-day maintenance", + rating: "4.0", + sla: "93%", + slaColor: "text-amber-400", + response: "3.2h", + openJobs: "3", + }, + ]; + + const fieldCrewData = [ + { + initials: "RK", + name: "Ravi Kumar", + role: "M&E Technician • Chiller plant", + status: "ON-SITE", + statusType: "onsite", + time: "6.4h today", + }, + { + initials: "SI", + name: "Sana Iqbal", + role: "HVAC Engineer • AHU room L7", + status: "ON-SITE", + statusType: "onsite", + time: "6.1h today", + }, + { + initials: "MD", + name: "Mohan D.", + role: "Electrician • En route", + status: "EN-ROUTE", + statusType: "enroute", + time: "—", + }, + { + initials: "LN", + name: "Lakshmi N.", + role: "Housekeeping Lead • Lobby + L2", + status: "ON-SITE", + statusType: "onsite", + time: "6.7h today", + }, + { + initials: "AP", + name: "Arjun P.", + role: "Plumber • Off-site", + status: "OFF-SITE", + statusType: "offsite", + time: "—", + }, + { + initials: "FR", + name: "Fatima R.", + role: "BMS Operator • Control room", + status: "ON-SITE", + statusType: "onsite", + time: "7.6h today", + }, + ]; + + return ( +
+ {/* --- HEADER SECTION --- */} +
+
+
+ Apple One — HQ Tower + + Operations + + Vendors +
+

+ Vendor & field service +

+

+ Vendor performance scorecards and live field-crew status in one view — SLA adherence, response times and job dispatch across your service partners. +

+
+ + {/* --- HEADER BUTTONS --- */} +
+ + + +
+
+ + {/* --- STAT CARDS --- */} +
+ {/* Active vendors */} + + + {/* Avg SLA adherence */} + + + {/* Open field jobs */} + + + {/* Avg response time */} + +
+ + {/* --- TABLES GRID (SIDE-BY-SIDE) --- */} +
+ + {/* LEFT TABLE: VENDOR SCORECARD */} +
+
+

Vendor scorecard

+ + SLA • rating • jobs + +
+ +
+ + + + + + + + + + + + + {vendorData.map((row, index) => ( + + + + + + + + + ))} + +
VendorScopeRatingSLAResponseOpen
+
{row.name}
+
{row.id}
+
+ {row.scope} + +
+ {row.rating} + +
+
+ {row.sla} + + {row.response} + + {row.openJobs} +
+
+
+ + {/* RIGHT TABLE: FIELD CREW • LIVE */} +
+
+

Field crew • live

+ + 4 on site + +
+ +
+ {fieldCrewData.map((crew, index) => { + return ( +
+
+
+ {crew.initials} +
+
+
{crew.name}
+
{crew.role}
+
+
+ +
+ {crew.statusType === "onsite" && ( + + + {crew.status} + + )} + {crew.statusType === "enroute" && ( + + + {crew.status} + + )} + {crew.statusType === "offsite" && ( + + + {crew.status} + + )} +
+ {crew.time} +
+
+
+ ); + })} +
+
+ +
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/components/dashboard/workpermit.tsx b/components/dashboard/workpermit.tsx new file mode 100644 index 0000000..f402ea8 --- /dev/null +++ b/components/dashboard/workpermit.tsx @@ -0,0 +1,352 @@ +import { useState } from 'react'; +import { Plus, ShieldCheck, Clock, Check, AlertTriangle, QrCode, FileText, Eye } from 'lucide-react'; +import Button from "@/utils/button"; +import { workPermitFilters } from '@/constants/constant'; +import FilterTabs from '@/utils/roundedFilters'; +import StatCard from '@/utils/dashStatCard'; +import InfoBanner from '@/utils/infoBanner'; + +export default function WorkPermitPortal() { + const [activeTab, setActiveTab] = useState('TFM Requests'); + const [activeFilter, setActiveFilter] = useState('All'); + + // Permit Data matching the UI + const permitsData = [ + { + permit: "PTW-2663", + workingType: "Work at Height", + location: "Atrium void", + contractor: "Blue Star", + validityDate: "Jun 26, 2026", + validityTime: "08:30–17:00", + crew: 2, + status: "ACTIVE", + actionText: "Close out", + isHighlighted: false, + }, + { + permit: "PTW-2664", + workingType: "Electrical Isolation", + location: "STP wet well", + contractor: "Sterling FM", + validityDate: "Jun 26, 2026", + validityTime: "09:00–13:00", + crew: 3, + status: "SUBMITTED", + actionText: "Approve", + isHighlighted: true, // Row with highlight bar + }, + { + permit: "PTW-2665", + workingType: "Confined Space", + location: "DG yard", + contractor: "In-house team", + validityDate: "Jun 26, 2026", + validityTime: "14:00–18:00", + crew: 4, + status: "APPROVED", + actionText: "Activate", + isHighlighted: false, + }, + { + permit: "PTW-2666", + workingType: "Hot Work", + location: "Roof terrace", + contractor: "Voltas Ltd", + validityDate: "Jun 25, 2026", + validityTime: "08:00–12:00", + crew: 5, + status: "EXPIRED", + hasViewIconOnly: true, + isHighlighted: false, + }, + { + permit: "PTW-2667", + workingType: "Work at Height", + location: "LT panel room", + contractor: "Blue Star", + validityDate: "Jun 25, 2026", + validityTime: "10:00–16:00", + crew: 2, + status: "CLOSED", + hasViewIconOnly: true, + isHighlighted: false, + }, + { + permit: "PTW-2668", + workingType: "Electrical Isolation", + location: "Chiller plant", + contractor: "Sterling FM", + validityDate: "Jun 26, 2026", + validityTime: "11:00–15:00", + crew: 3, + status: "REJECTED", + hasViewIconOnly: true, + isHighlighted: false, + }, + { + permit: "PTW-2726", + workingType: "Work at Height", + location: "Atrium void", + contractor: "Blue Star", + validityDate: "Jun 26, 2026", + validityTime: "08:30–17:00", + crew: 2, + status: "DRAFT", + actionText: "Submit", + isHighlighted: false, + }, + ]; + + const renderStatusBadge = (status: string) => { + switch (status) { + case "ACTIVE": + return ( + + + ACTIVE + + ); + case "SUBMITTED": + return ( + + + SUBMITTED + + ); + case "APPROVED": + return ( + + + APPROVED + + ); + case "EXPIRED": + return ( + + + EXPIRED + + ); + case "CLOSED": + return ( + + + CLOSED + + ); + case "REJECTED": + return ( + + + REJECTED + + ); + case "DRAFT": + return ( + + + DRAFT + + ); + default: + return null; + } + }; + + return ( +
+ {/* --- HEADER SECTION --- */} +
+
+
+ Apple One — HQ Tower + + Operations + + Work Permit +
+

+ Permit to Work +

+

+ A controlled permit-to-work lifecycle — request, risk review, approval, activation and close-out — per site and hazard type. QR-driven on the floor. +

+
+ + {/* Action Buttons */} +
+ + + +
+
+ + {/* --- NAVIGATION TABS & TOP ACTION BUTTONS --- */} +
+ {/* Navigation tabs component if needed */} +
+ + {/* --- STAT CARDS --- */} +
+ {/* Active on site */} + + + {/* Awaiting approval */} + + + {/* Approved · ready */} + + + {/* Expired · breach */} + +
+ + {/* --- STATUS PILL FILTERS --- */} +
+ +
+ + {/* --- PERMIT TABLE SECTION --- */} +
+ + + + + + + + + + + + + + + {permitsData.map((row, index) => ( + + {/* Permit ID */} + + + {/* Working Type */} + + + {/* Location */} + + + {/* Contractor */} + + + {/* Validity */} + + + {/* Crew */} + + + {/* Status Badge */} + + + {/* Actions */} + + + ))} + +
PermitWorking TypeLocationContractorValidityCrewStatus
+ {row.isHighlighted && ( +
+ )} + {row.permit} +
+ {row.workingType} + + {row.location} + + {row.contractor} + +
{row.validityDate}
+
{row.validityTime}
+
+ {row.crew} + + {renderStatusBadge(row.status)} + +
+ {row.hasViewIconOnly ? ( +
+ + +
+ ) : ( +
+ + +
+ )} +
+
+
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/constants/constant.ts b/constants/constant.ts index ea88dc8..1effc72 100644 --- a/constants/constant.ts +++ b/constants/constant.ts @@ -216,6 +216,16 @@ export const Reqeusttabs: string[] = [ ]; + export const workPermitFilters = [ + { label: 'All', count: 10 }, + { label: 'Awaiting', count: 3 }, + { label: 'Approved', count: 1}, + { label: 'Active', count: 2 }, + { label: 'Closed', count: 1 }, + { label: 'Expired', count: 1 }, + { label: 'Rejected', count: 1 }, + ]; + // Data model interface @@ -232,4 +242,5 @@ export const Reqeusttabs: string[] = [ { 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' }, - ]; \ No newline at end of file + ]; + \ No newline at end of file diff --git a/constants/types.ts b/constants/types.ts index 1c6d66f..aaed3bb 100644 --- a/constants/types.ts +++ b/constants/types.ts @@ -101,4 +101,17 @@ export interface FilterTabsProps { export interface InfoBannerProps { boldText?: string; normalText?: string; -} \ No newline at end of file +} +export 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'; +} diff --git a/utils/table.tsx b/utils/dataTable.tsx similarity index 97% rename from utils/table.tsx rename to utils/dataTable.tsx index 748462f..36b2d52 100644 --- a/utils/table.tsx +++ b/utils/dataTable.tsx @@ -1,199 +1,397 @@ import { useState } from 'react'; + import { + Pencil, + Menu, + ShieldCheck, + QrCode, + Power, + Check, + Sparkles, + } from 'lucide-react'; + import { mockData } from '@/constants/constant'; + + + export default function LocationTable(){ + const [selectedIds, setSelectedIds] = useState(['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 ( +
+
+ + {/* TABLE HEADER */} + + + + + + + + + + + + + + + + + + {/* TABLE BODY */} + + {mockData.map((row) => { + const isSelected = selectedIds.includes(row.id); + + return ( + + {/* Left Selection Indicator Bar (for Row 1 / Selected Rows) */} + + + {/* Block */} + + + {/* Floor */} + + + {/* Zone */} + + + {/* Department */} + + + {/* Location */} + + + {/* Window */} + + + {/* TAT */} + + + {/* Sched */} + + + {/* Approval */} + + + {/* Status Badge */} + + + {/* Action Icon Buttons */} + + + ); + })} + +
+ + BLOCKFLOORZONEDEPARTMENTLOCATIONWINDOWTATSCHED.APPROVALSTATUSACTIONS
+ {isSelected && ( + + )} + 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" + /> + + {row.block} + + {row.floor} + + {row.zone} + + {row.department} + + {row.location} + + {row.window} + + {row.tat} + + {row.sched} + + {row.approval ? ( + + + Yes + + ) : ( + —— + )} + + {row.status === 'ACTIVE' ? ( + + + ACTIVE + + ) : ( + + + INACTIVE + + )} + +
+ + + + + +
+
+
+ + +
+ ); -} \ No newline at end of file + +} \ No newline at end of file