HBT-software / src /components /StaffManager.tsx
embedingHF's picture
Upload folder using huggingface_hub
46463e1 verified
Raw
History Blame Contribute Delete
15.9 kB
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import {
Users,
UserCheck,
Lock,
Shield,
Trash2,
UserPlus,
Fingerprint,
CheckCircle2,
XCircle,
ToggleLeft,
KeyRound,
Sliders,
Settings
} from 'lucide-react';
import { StaffUser, StaffRole } from '../types';
interface StaffManagerProps {
staffList: StaffUser[];
currentStaff: StaffUser;
onSetCurrentStaff: (staff: StaffUser) => void;
onAddStaffMember: (user: StaffUser) => void;
onToggleStaffStatus: (staffId: string) => void;
onUpdateStaffRole: (staffId: string, role: StaffRole) => void;
}
export default function StaffManager({
staffList,
currentStaff,
onSetCurrentStaff,
onAddStaffMember,
onToggleStaffStatus,
onUpdateStaffRole
}: StaffManagerProps) {
const [showAddStaff, setShowAddStaff] = useState(false);
// Create staff states
const [newName, setNewName] = useState('');
const [newEmail, setNewEmail] = useState('');
const [newRole, setNewRole] = useState<StaffRole>('cashier');
const isOwner = currentStaff.role === 'owner';
const isManagerOrOwner = currentStaff.role === 'owner' || currentStaff.role === 'manager';
const handleCreateStaff = (e: React.FormEvent) => {
e.preventDefault();
if (!newName.trim() || !newEmail.trim()) {
alert("Name and email references are mandatory.");
return;
}
if (staffList.some(s => s.email.toLowerCase() === newEmail.toLowerCase())) {
alert("An account is already configured under this email register.");
return;
}
const newUser: StaffUser = {
id: 'staff_' + Math.random().toString(36).substr(2, 9),
name: newName,
email: newEmail,
role: newRole,
active: true
};
onAddStaffMember(newUser);
setNewName('');
setNewEmail('');
setNewRole('cashier');
setShowAddStaff(false);
};
const handleSwitchOperator = (user: StaffUser) => {
if (!user.active) {
alert("Cannot switch to inactive staff profile. Owner must restore authorization status first.");
return;
}
onSetCurrentStaff(user);
};
// Role details mapping for easy visual display
const getRoleBadgeColor = (role: StaffRole) => {
switch(role) {
case 'owner': return 'bg-slate-900 text-white border-transparent';
case 'manager': return 'bg-slate-100 text-slate-800 border-slate-200';
case 'cashier': return 'bg-slate-50 text-slate-650 border-slate-200/60';
}
};
return (
<div className="space-y-6" id="role-vault-cockpit">
{/* Visual Identity Widget - Active privileges indicator */}
<div className="bg-white border border-slate-200 rounded-xl p-5 flex flex-col md:flex-row items-start md:items-center justify-between gap-6 shadow-sm">
<div className="flex items-center gap-4">
<div className="p-3.5 bg-slate-900 text-white rounded-lg shadow-sm">
<Fingerprint className="w-6 h-6 text-slate-100" />
</div>
<div className="space-y-1">
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold block">Authorized Terminal Cashier</span>
<div className="flex items-center gap-2">
<h3 className="text-base font-bold text-slate-900 leading-none">{currentStaff.name}</h3>
<span className={`text-[9px] font-bold px-2 py-0.5 border rounded-md capitalize ${getRoleBadgeColor(currentStaff.role)}`}>
{currentStaff.role} MODE
</span>
</div>
<p className="text-xs text-slate-400">Security ledger email: {currentStaff.email}</p>
</div>
</div>
<div className="bg-slate-50 border border-slate-200/80 p-3.5 rounded-lg space-y-1 md:max-w-xs text-xs font-sans">
<span className="font-bold text-slate-800 flex items-center gap-1.5 uppercase tracking-wider text-[10px]">
<Shield className="w-3.5 h-3.5 text-slate-755" />
Enforced Role Clearances
</span>
<p className="text-slate-550 leading-relaxed text-[11px]">
{currentStaff.role === 'owner' ? "Owner clearance active. Unlimited access of warehouse multipliers, revenue parameters, and full backups." :
currentStaff.role === 'manager' ? "Manager clearance active. Full access to manual stock adjusters, inventory updates, and cashier rosters." :
"Cashier clearance active. Invoice generation, payments checkout, and outreach promotions. Stock manual modifications locked."}
</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* LEFT COLUMN: Switch Active Terminal Operator Block */}
<div className="lg:col-span-4 bg-white border border-slate-200 rounded-xl shadow-sm p-5 space-y-4">
<div className="space-y-1 border-b border-slate-100 pb-3">
<h3 className="text-xs font-bold text-slate-950 uppercase flex items-center gap-1.5">
<KeyRound className="w-4 h-4 text-slate-705" />
Switch Terminal Operator
</h3>
<p className="text-xs text-slate-400">Review security flows by logging into alternative staff portfolios</p>
</div>
<div className="space-y-2.5">
{staffList.map((user) => {
const isActiveLocal = user.id === currentStaff.id;
return (
<div
key={user.id}
onClick={() => handleSwitchOperator(user)}
className={`border p-3 rounded-lg flex items-center justify-between transition cursor-pointer ${
!user.active ? 'bg-slate-50 border-slate-100 opacity-55 cursor-not-allowed' :
isActiveLocal
? 'bg-slate-900 text-white border-transparent shadow-sm font-semibold'
: 'bg-white hover:border-slate-350 hover:bg-slate-50 border-slate-200'
}`}
>
<div className="space-y-0.5">
<p className={`text-xs ${isActiveLocal ? 'text-white' : 'text-slate-900'} font-bold`}>{user.name}</p>
<span className={`text-[10px] capitalize block ${isActiveLocal ? 'text-slate-300 font-medium' : 'text-slate-400'}`}>
{user.role} • <span>{user.active ? 'Authorized' : 'Suspended'}</span>
</span>
</div>
{isActiveLocal ? (
<div className="bg-white/10 p-1 rounded-md text-white">
<UserCheck className="w-4 h-4" />
</div>
) : (
<span className="text-[10px] text-slate-800 font-semibold underline hover:no-underline">Log in</span>
)}
</div>
);
})}
</div>
</div>
{/* RIGHT COLUMN: Full Staff registers with Bounded Controls */}
<div className="lg:col-span-8 bg-white border border-slate-200 rounded-xl shadow-sm p-5 space-y-4">
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
<div className="space-y-1">
<h3 className="text-xs font-bold text-slate-950 uppercase flex items-center gap-1.5">
<Users className="w-4 h-4 text-slate-705" />
Staff Directory &amp; RBAC Registers
</h3>
<p className="text-xs text-slate-400">Edit employee details, adjust authorization levels, and configure status settings</p>
</div>
{isManagerOrOwner && (
<button
type="button"
onClick={() => setShowAddStaff(true)}
className="bg-slate-900 hover:bg-slate-800 text-white font-medium text-xs px-3 py-2 rounded-lg transition shadow-sm flex items-center gap-1.5 cursor-pointer"
>
<UserPlus className="w-4 h-4" />
Register Staff
</button>
)}
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs font-sans">
<thead className="bg-slate-50 font-bold uppercase text-[9px] tracking-widest text-slate-500 leading-none">
<tr className="border-b border-slate-150">
<th className="py-3 px-4">Employee Card</th>
<th className="py-3 px-4">System Access Tier</th>
<th className="py-3 px-4 text-center">Duty Status</th>
<th className="py-3 px-4 text-right">Administrative overrides</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 align-middle">
{staffList.map((user) => (
<tr key={user.id} className={!user.active ? 'opacity-60 bg-slate-50/50' : ''}>
<td className="py-3.5 px-4">
<div className="space-y-0.5">
<p className="font-bold text-slate-900">{user.name}</p>
<p className="text-[10px] text-slate-500">{user.email}</p>
</div>
</td>
<td className="py-3.5 px-4 font-sans font-bold">
{isOwner && user.id !== currentStaff.id ? (
<select
value={user.role}
onChange={(e) => onUpdateStaffRole(user.id, e.target.value as StaffRole)}
className="border border-slate-200 bg-white p-1 rounded font-sans text-xs outline-none"
>
<option value="owner">Owner</option>
<option value="manager">Manager</option>
<option value="cashier">Cashier</option>
</select>
) : (
<span className={`text-[9px] font-bold px-2 py-0.5 border rounded-full capitalize ${getRoleBadgeColor(user.role)}`}>
{user.role}
</span>
)}
</td>
<td className="py-3.5 px-4 text-center">
<div className="flex items-center justify-center gap-1">
{user.active ? (
<span className="bg-emerald-50 text-emerald-800 border border-emerald-100 text-[10px] px-2 py-0.5 rounded-full font-semibold flex items-center gap-1">
<CheckCircle2 className="w-3 h-3 text-emerald-600" />
Active
</span>
) : (
<span className="bg-red-50 text-red-800 border border-red-100 text-[10px] px-2 py-0.5 rounded-full font-semibold flex items-center gap-1">
<XCircle className="w-3 h-3 text-red-500 animate-pulse" />
Suspended
</span>
)}
</div>
</td>
<td className="py-3.5 px-4 text-right">
{user.id === currentStaff.id ? (
<span className="text-[10px] text-slate-400 italic">Operating account</span>
) : isManagerOrOwner ? (
<button
type="button"
onClick={() => onToggleStaffStatus(user.id)}
className={`text-xs font-semibold px-3 py-1 rounded-lg transition border cursor-pointer ${
user.active
? 'text-red-600 bg-red-50 hover:bg-red-100 border-red-150'
: 'text-emerald-700 bg-emerald-50 hover:bg-emerald-100 border-emerald-150'
}`}
>
{user.active ? 'Deauthorize' : 'Authorize Duty'}
</button>
) : (
<span className="text-[10px] text-slate-400 italic flex items-center gap-1 justify-end">
<Lock className="w-3.5 h-3.5" /> Locked
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* CORE MODAL: REGISTER STAFF MEMBER */}
{showAddStaff && (
<div className="fixed inset-0 bg-slate-900/40 backdrop-blur-[2px] z-50 flex items-center justify-center p-4">
<div className="bg-white rounded-xl border border-slate-200 max-w-md w-full p-6 space-y-5 relative shadow-xl">
<button
onClick={() => setShowAddStaff(false)}
className="absolute top-4 right-4 bg-slate-50 hover:bg-slate-150 text-slate-400 hover:text-slate-800 font-bold px-2.5 py-1 rounded text-xs cursor-pointer"
>
</button>
<div className="pb-2 border-b border-slate-100">
<h3 className="text-sm font-bold text-slate-900 uppercase">Register New Terminal Account</h3>
<p className="text-xs text-slate-400">Provide credentials and role permission clearings</p>
</div>
<form onSubmit={handleCreateStaff} className="space-y-4 text-xs font-sans">
<div className="space-y-1">
<label className="text-[10px] text-slate-500 font-bold uppercase">Name / Identification</label>
<input
type="text"
required
placeholder="e.g. Zain Brother"
value={newName}
onChange={(e) => setNewName(e.target.value)}
className="w-full border border-slate-200 bg-white px-3 py-2 rounded-lg outline-none focus:border-slate-800 transition"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500 font-bold uppercase">Secure Email register</label>
<input
type="email"
required
placeholder="e.g. zain@traders.com"
value={newEmail}
onChange={(e) => setNewEmail(e.target.value)}
className="w-full border border-slate-200 bg-white px-3 py-2 rounded-lg outline-none focus:border-slate-800 transition"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500 font-bold uppercase">Assigned Security Role Clearance</label>
<select
value={newRole}
onChange={(e) => setNewRole(e.target.value as StaffRole)}
className="w-full border border-slate-200 bg-white px-3 py-2 rounded-lg outline-none focus:border-slate-800 transition text-xs"
>
<option value="cashier">Cashier clearance level (Draft bills &amp; Payments)</option>
<option value="manager">Manager clearance level (Adjustments &amp; Inventory config)</option>
<option value="owner">Owner credentials clearance (Total configuration overrides)</option>
</select>
</div>
<button
type="submit"
className="w-full bg-slate-900 hover:bg-slate-800 text-white font-medium py-2.5 rounded-lg shadow-sm transition cursor-pointer text-center"
>
Register Employee Account
</button>
</form>
</div>
</div>
)}
</div>
);
}