Files
KAFM-Project/components/dashboard/operations/workpermit.tsx

352 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react';
import { Plus, ShieldCheck, Clock, Check, AlertTriangle, QrCode, FileText, Eye } from 'lucide-react';
import Button from "@/components/common/button";
import { workPermitFilters } from '@/constants/constant';
import FilterTabs from '@/components/common/roundedFilters';
import StatCard from '@/components/common/dashStatCard';
import InfoBanner from '@/components/common/infoBanner';
export default function WorkPermitPortal() {
const [activeFilter, setActiveFilter] = useState<string>('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:3017: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:0013: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:0018: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:0012: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:0016: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:0015: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:3017:00",
crew: 2,
status: "DRAFT",
actionText: "Submit",
isHighlighted: false,
},
];
const renderStatusBadge = (status: string) => {
switch (status) {
case "ACTIVE":
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold tracking-wider bg-emerald-950/60 text-emerald-400 border border-emerald-800/40">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
ACTIVE
</span>
);
case "SUBMITTED":
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold tracking-wider bg-amber-950/50 text-amber-300 border border-amber-800/40">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400"></span>
SUBMITTED
</span>
);
case "APPROVED":
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold tracking-wider bg-blue-950/60 text-blue-400 border border-blue-800/40">
<span className="w-1.5 h-1.5 rounded-full bg-blue-400"></span>
APPROVED
</span>
);
case "EXPIRED":
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold tracking-wider bg-rose-950/40 text-rose-400 border border-rose-900/30">
<span className="w-1.5 h-1.5 rounded-full bg-rose-400"></span>
EXPIRED
</span>
);
case "CLOSED":
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold tracking-wider bg-slate-800/80 text-slate-400 border border-slate-700/50">
<span className="w-1.5 h-1.5 rounded-full bg-slate-400"></span>
CLOSED
</span>
);
case "REJECTED":
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold tracking-wider bg-rose-950/40 text-rose-400 border border-rose-900/30">
<span className="w-1.5 h-1.5 rounded-full bg-rose-400"></span>
REJECTED
</span>
);
case "DRAFT":
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold tracking-wider bg-slate-800/80 text-slate-400 border border-slate-700/50">
<span className="w-1.5 h-1.5 rounded-full bg-slate-400"></span>
DRAFT
</span>
);
default:
return null;
}
};
return (
<div className="my-15 text-slate-100 font-sans">
{/* --- HEADER SECTION --- */}
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<div>
<div className="flex items-center gap-2 text-[10px] font-bold tracking-widest text-slate-500 uppercase font-mono">
<span className="text-teal-400">Apple One HQ Tower</span>
<span></span>
<span>Operations</span>
<span></span>
<span>Work Permit</span>
</div>
<h1 className="text-2xl font-bold text-white tracking-tight mt-2">
Permit to Work
</h1>
<p className="text-sm font-medium text-slate-400 mt-2 max-w-xl leading-relaxed">
A controlled permit-to-work lifecycle request, risk review, approval, activation and close-out per site and hazard type. QR-driven on the floor.
</p>
</div>
{/* Action Buttons */}
<div className="flex items-center gap-3 self-start md:mt-10">
<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">
<QrCode size={16} />
<span>Scan 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 permit</span>
</Button>
</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 component if needed */}
</div>
{/* --- STAT CARDS --- */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
{/* Active on site */}
<StatCard
variant="icon"
title="Active on site"
value="2"
icon={ShieldCheck}
badgeText="live"
badgeClassName="text-teal-400 font-mono font-medium"
iconContainerClassName="bg-emerald-950/60 border-emerald-800/40 text-emerald-400"
/>
{/* Awaiting approval */}
<StatCard
variant="icon"
title="Awaiting approval"
value="3"
icon={Clock}
badgeText="action due"
badgeClassName="text-amber-500 font-mono font-medium"
iconContainerClassName="bg-amber-950/40 border-amber-800/30 text-amber-500"
/>
{/* Approved · ready */}
<StatCard
variant="icon"
title="Approved · ready"
value="1"
icon={Check}
badgeText="to activate"
badgeClassName="text-slate-400 font-mono"
iconContainerClassName="bg-blue-950/50 border-blue-800/30 text-blue-400"
/>
{/* Expired · breach */}
<StatCard
variant="icon"
title="Expired · breach"
value="1"
icon={AlertTriangle}
badgeText="escalate"
badgeClassName="text-rose-400 font-mono font-medium"
iconContainerClassName="bg-rose-950/50 border-rose-800/30 text-rose-400"
/>
</div>
{/* --- STATUS PILL FILTERS --- */}
<div className="mb-6">
<FilterTabs
options={workPermitFilters}
activeFilter={activeFilter}
onChange={setActiveFilter}
/>
</div>
{/* --- PERMIT TABLE SECTION --- */}
<div className="bg-[#0b1329] border border-slate-800/80 rounded-lg overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-slate-800/80 text-[11px] font-bold uppercase tracking-wider text-slate-400">
<th className="py-3 px-4">Permit</th>
<th className="py-3 px-4">Working Type</th>
<th className="py-3 px-4">Location</th>
<th className="py-3 px-4">Contractor</th>
<th className="py-3 px-4">Validity</th>
<th className="py-3 px-4">Crew</th>
<th className="py-3 px-4">Status</th>
<th className="py-3 px-4 text-right"></th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/40">
{permitsData.map((row, index) => (
<tr
key={index}
className={`transition-colors relative ${
row.isHighlighted ? "bg-[#0e2238]" : "hover:bg-slate-800/30"
}`}
>
{/* Permit ID */}
<td className="py-4 px-4 font-mono text-teal-400 font-medium text-xs whitespace-nowrap relative">
{row.isHighlighted && (
<div className="absolute left-0 top-0 bottom-0 w-[3px] bg-teal-400" />
)}
{row.permit}
</td>
{/* Working Type */}
<td className="py-4 px-4 font-semibold text-slate-100 whitespace-nowrap">
{row.workingType}
</td>
{/* Location */}
<td className="py-4 px-4 text-slate-300 font-medium whitespace-nowrap">
{row.location}
</td>
{/* Contractor */}
<td className="py-4 px-4 text-slate-300 font-medium whitespace-nowrap">
{row.contractor}
</td>
{/* Validity */}
<td className="py-4 px-4 whitespace-nowrap">
<div className="text-xs text-slate-300 font-medium">{row.validityDate}</div>
<div className="text-[11px] font-mono text-slate-500">{row.validityTime}</div>
</td>
{/* Crew */}
<td className="py-4 px-4 text-slate-300 font-medium whitespace-nowrap">
{row.crew}
</td>
{/* Status Badge */}
<td className="py-4 px-4 whitespace-nowrap">
{renderStatusBadge(row.status)}
</td>
{/* Actions */}
<td className="py-4 px-4 text-right whitespace-nowrap">
<div className="flex items-center justify-end gap-2">
{row.hasViewIconOnly ? (
<div className="flex flex-col gap-1">
<button className="p-1.5 text-slate-400 hover:text-white bg-slate-800/40 hover:bg-slate-800 rounded border border-slate-700/50 transition-colors">
<FileText size={14} />
</button>
<button className="p-1.5 text-slate-400 hover:text-white bg-slate-800/40 hover:bg-slate-800 rounded border border-slate-700/50 transition-colors">
<Eye size={14} />
</button>
</div>
) : (
<div className="flex flex-col items-center gap-1">
<button className="p-1.5 text-slate-400 hover:text-white bg-slate-800/40 hover:bg-slate-800 rounded border border-slate-700/50 transition-colors">
<FileText size={14} />
</button>
<button className="text-teal-400 hover:text-teal-300 text-xs font-semibold hover:underline">
{row.actionText}
</button>
</div>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mb-10 mt-10">
<InfoBanner
boldText="Compliance:"
normalText="a permit must be Approved before work starts and Closed on completion. Permits past their validity window auto-flag as Expired for safety escalation."
/>
</div>
</div>
);
}