3import { useEffect, useState } from "react";
4import { apiClient } from "@/lib/client/apiClient";
5import { Icon } from "@/contexts/IconContext";
10 f102: number; // Active (1/0)
11 f107?: string; // Barcode
12 f121?: string; // Formal Name
13 f122?: string; // Main Location
14 f123?: string; // Printed Name
15 f124?: string; // First Name
16 f125?: string; // Last Name
17 f126?: string; // Nick Name
18 f127?: string; // Start Date
19 f128?: string; // Leave Date
20 f130?: string; // Private Email
21 f131?: string; // Work Email
22 f133?: string; // Online Login
23 f134?: number; // Is Customer Rep
24 f135?: number; // Is Account Rep
25 f136?: string; // Remote Profile
26 f137?: string; // Auto Login From
27 f143?: number; // Login Where bitmask
28 LastOk?: string; // Last seen datetime
31interface SecurityRole {
37interface StaffFormData {
54 onlinePassword: string;
55 isCustomerRep: boolean;
56 isAccountRep: boolean;
57 remoteProfile: number;
58 autoLoginFrom: string;
64 f101: string; // Short Name
65 f102: string; // Full Name
66 f103?: string; // POS Login ID
67 f104?: string; // Start Date
68 f105?: string; // End Date
69 f106?: string; // Main Location
70 f107?: string; // Job Title
71 f108?: string; // Job Code
72 f109?: string; // Department ID
73 f110?: string; // External ID
74 f114?: string; // NZTA UID
77export default function StaffManagementPage() {
78 const [activeTab, setActiveTab] = useState<'staff' | 'employees'>('staff');
79 const [staff, setStaff] = useState<StaffMember[]>([]);
80 const [filteredStaff, setFilteredStaff] = useState<StaffMember[]>([]);
81 const [securityRoles, setSecurityRoles] = useState<SecurityRole[]>([]);
82 const [selectedRoles, setSelectedRoles] = useState<number[]>([]);
83 const [loading, setLoading] = useState(true);
84 const [searchTerm, setSearchTerm] = useState("");
85 const [showActive, setShowActive] = useState(true);
86 const [showInactive, setShowInactive] = useState(true);
87 const [showRole, setShowRole] = useState(false);
88 const [showModal, setShowModal] = useState(false);
89 const [editingStaff, setEditingStaff] = useState<StaffMember | null>(null);
92 const [employees, setEmployees] = useState<Employee[]>([]);
93 const [filteredEmployees, setFilteredEmployees] = useState<Employee[]>([]);
94 const [employeeSearchTerm, setEmployeeSearchTerm] = useState("");
95 const [loadingEmployees, setLoadingEmployees] = useState(false);
96 const [showEmployeeModal, setShowEmployeeModal] = useState(false);
97 const [editingEmployee, setEditingEmployee] = useState<Employee | null>(null);
98 const [formData, setFormData] = useState<StaffFormData>({
115 isCustomerRep: false,
119 loginWhere: 1, // POS by default
127 if (activeTab === 'employees') {
133 applyEmployeeFilters();
134 }, [employees, employeeSearchTerm]);
138 }, [staff, searchTerm, showActive, showInactive, showRole]);
140 const loadStaff = async () => {
143 const [staffResult, usageResult] = await Promise.all([
144 apiClient.getStaff(),
145 apiClient.getStaffUsage()
148 if (staffResult.success && staffResult.data?.DATS) {
149 const staffList = staffResult.data.DATS;
151 // Extract security roles (negative IDs)
152 const roles = staffList
153 .filter((s: StaffMember) => s.f100 < 0 && s.f102 === 1)
154 .map((s: StaffMember) => ({
157 active: s.f102 === 1,
159 setSecurityRoles(roles);
162 if (usageResult.success && usageResult.data?.DATS) {
163 const usageMap = new Map(
164 usageResult.data.DATS.map((u: any) => [u.f110, u.f112])
167 staffList.forEach((s: StaffMember) => {
168 if (usageMap.has(s.f100)) {
169 s.LastOk = usageMap.get(s.f100) as string | undefined;
177 console.error("Error loading staff:", error);
183 const applyFilters = () => {
184 let filtered = staff.filter((member) => {
185 const isActive = member.f102 === 1;
186 const isRole = member.f100 < 0;
188 // Filter by active/inactive/role
189 if (isRole && !showRole) return false;
190 if (!isRole && isActive && !showActive) return false;
191 if (!isRole && !isActive && !showInactive) return false;
193 // Filter by search term
195 const search = searchTerm.toLowerCase();
197 member.f101?.toLowerCase().includes(search) ||
198 member.f107?.toLowerCase().includes(search) ||
199 member.f100?.toString().includes(search)
206 setFilteredStaff(filtered);
209 const openNewStaffModal = () => {
210 setEditingStaff(null);
211 setSelectedRoles([]);
229 isCustomerRep: false,
238 const openEditModal = (member: StaffMember) => {
239 setEditingStaff(member);
242 name: member.f101 || "",
243 active: member.f102 === 1,
245 barcode: member.f107 || "",
246 formalName: member.f121 || "",
247 mainLocation: member.f122 || "",
248 printedName: member.f123 || "",
249 firstName: member.f124 || "",
250 lastName: member.f125 || "",
251 nickName: member.f126 || "",
252 startDate: member.f127 || "",
253 leaveDate: member.f128 || "",
254 privateEmail: member.f130 || "",
255 workEmail: member.f131 || "",
256 onlineLogin: member.f133 || "",
258 isCustomerRep: member.f134 === 1,
259 isAccountRep: member.f135 === 1,
260 remoteProfile: parseInt(member.f136 || "0"),
261 autoLoginFrom: member.f137 || "",
262 loginWhere: member.f143 || 1,
267 const handleSave = async () => {
269 const saveData: any = { ...formData };
271 // Add selected roles for new staff
272 if (!editingStaff && selectedRoles.length > 0) {
273 saveData.roles = selectedRoles;
276 const result = await apiClient.saveStaff(saveData);
278 if (result.success) {
282 alert("Failed to save staff: " + (result.error || "Unknown error"));
285 console.error("Error saving staff:", error);
286 alert("Failed to save staff member");
290 const formatDateTime = (dateStr: string) => {
291 if (!dateStr) return "—";
292 const date = new Date(dateStr);
293 return date.toLocaleString("en-GB", {
302 // Login log functions
303 const loadEmployees = async () => {
305 setLoadingEmployees(true);
306 const result = await apiClient.getEmployees();
308 if (result.success && result.data?.DATS) {
309 setEmployees(result.data.DATS);
310 setFilteredEmployees(result.data.DATS);
313 console.error("Error loading employees:", error);
315 setLoadingEmployees(false);
319 const applyEmployeeFilters = () => {
320 if (!employeeSearchTerm) {
321 setFilteredEmployees(employees);
325 const search = employeeSearchTerm.toLowerCase();
326 const filtered = employees.filter((emp) => {
328 emp.f100?.toString().includes(search) ||
329 emp.f101?.toLowerCase().includes(search) ||
330 emp.f102?.toLowerCase().includes(search) ||
331 emp.f107?.toLowerCase().includes(search) ||
332 emp.f110?.toLowerCase().includes(search)
336 setFilteredEmployees(filtered);
339 const openNewEmployeeModal = () => {
354 setShowEmployeeModal(true);
357 const openEditEmployeeModal = (employee: Employee) => {
358 setEditingEmployee(employee);
359 setShowEmployeeModal(true);
362 const handleSaveEmployee = async () => {
364 const saveData: any = {
365 shortName: editingEmployee?.f101 || "",
366 fullName: editingEmployee?.f102 || "",
367 posLoginId: editingEmployee?.f103,
368 startDate: editingEmployee?.f104,
369 endDate: editingEmployee?.f105,
370 mainLocation: editingEmployee?.f106,
371 jobTitle: editingEmployee?.f107,
372 jobCode: editingEmployee?.f108,
373 departmentId: editingEmployee?.f109,
374 externalId: editingEmployee?.f110,
375 nztaUid: editingEmployee?.f114,
378 if (editingEmployee?.f100) {
379 saveData.id = editingEmployee.f100;
382 const result = await apiClient.saveEmployee(saveData);
384 if (result.success) {
385 setShowEmployeeModal(false);
386 setEditingEmployee(null);
389 alert("Failed to save employee: " + (result.error || "Unknown error"));
392 console.error("Error saving employee:", error);
393 alert("Failed to save employee");
399 <div className="p-6 bg-bg min-h-screen">
400 <div className="animate-pulse space-y-4">
401 <div className="h-8 bg-surface rounded w-1/4"></div>
402 <div className="h-64 bg-surface rounded"></div>
409 <div className="p-6 bg-bg min-h-screen">
411 <div className="mb-6 flex items-center gap-3">
412 <Icon name="badge" size={36} className="text-brand" />
414 <h1 className="text-3xl font-bold text-text">
417 <p className="text-muted">
418 Manage POS staff, employees, security roles, and login history
424 <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
425 <div className="bg-brand/10 border-l-4 border-brand p-6 rounded-lg">
426 <div className="flex items-start gap-4">
427 <Icon name="person" size={40} className="text-brand flex-shrink-0" />
429 <h3 className="text-lg font-bold text-text mb-2 flex items-center gap-2">
430 <Icon name="person" size={20} />Staff (POS Operators)
432 <p className="text-sm text-text mb-2">
433 <strong>POS system users</strong> with login credentials for cash registers and terminals.
435 <ul className="text-xs text-muted space-y-1">
436 <li>• Have POS login access and passwords</li>
437 <li>• Assigned to security roles for permissions</li>
438 <li>• Track sales, transactions, and till operations</li>
439 <li>• Can log in to selling interface</li>
445 <div className="bg-success/10 border-l-4 border-success p-6 rounded-lg">
446 <div className="flex items-start gap-4">
447 <Icon name="groups" size={40} className="text-success flex-shrink-0" />
449 <h3 className="text-lg font-bold text-text mb-2 flex items-center gap-2">
450 <Icon name="groups" size={20} />Employees (HR Records)
452 <p className="text-sm text-text mb-2">
453 <strong>Workforce records</strong> for commission tracking, payroll, and HR management.
455 <ul className="text-xs text-muted space-y-1">
456 <li>• NO POS login access</li>
457 <li>• Track commissions and payroll data</li>
458 <li>• Job titles, departments, hire dates</li>
459 <li>• May be linked to external payroll systems</li>
466 {/* Tab Navigation */}
467 <div className="bg-surface rounded-lg shadow mb-6 p-2">
468 <div className="grid grid-cols-2 gap-2">
470 onClick={() => setActiveTab('staff')}
471 className={`px-4 py-4 font-semibold text-base rounded-lg transition-all ${
472 activeTab === 'staff'
473 ? 'bg-brand text-white shadow-lg transform scale-105'
474 : 'bg-surface-2 text-muted hover:bg-surface'
477 <div className="flex flex-col items-center justify-center gap-1">
478 <Icon name="person" size={24} />
479 <span>Staff (POS)</span>
483 onClick={() => setActiveTab('employees')}
484 className={`px-4 py-4 font-semibold text-base rounded-lg transition-all ${
485 activeTab === 'employees'
486 ? 'bg-success text-white shadow-lg transform scale-105'
487 : 'bg-surface-2 text-muted hover:bg-surface'
490 <div className="flex flex-col items-center justify-center gap-1">
491 <Icon name="groups" size={24} />
492 <span>Employees (HR)</span>
498 {/* Staff List View */}
499 {activeTab === 'staff' && (
502 <div className="bg-surface rounded-lg shadow p-4 mb-6">
503 <div className="flex flex-wrap items-center gap-4">
505 onClick={openNewStaffModal}
506 className="bg-brand text-white px-6 py-2 rounded-lg hover:bg-brand/90 transition-colors flex items-center gap-2"
508 <Icon name="add" size={20} />
512 <div className="flex-1 min-w-[200px] relative">
513 <Icon name="search" size={20} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted" />
516 placeholder="Search staff..."
518 onChange={(e) => setSearchTerm(e.target.value)}
519 className="w-full pl-10 pr-4 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
523 <div className="flex items-center gap-4">
524 <label className="flex items-center gap-2 cursor-pointer">
528 onChange={(e) => setShowActive(e.target.checked)}
529 className="w-4 h-4 text-brand rounded"
531 <span className="text-sm text-text">Active</span>
533 <label className="flex items-center gap-2 cursor-pointer">
536 checked={showInactive}
537 onChange={(e) => setShowInactive(e.target.checked)}
538 className="w-4 h-4 text-brand rounded"
540 <span className="text-sm text-text">Inactive</span>
542 <label className="flex items-center gap-2 cursor-pointer">
546 onChange={(e) => setShowRole(e.target.checked)}
547 className="w-4 h-4 text-brand rounded"
549 <span className="text-sm text-text">Roles Only</span>
554 <div className="mt-2 text-sm text-muted">
555 Displaying {filteredStaff.length} of {staff.length} staff members
560 <div className="bg-surface rounded-lg shadow overflow-hidden">
561 <div className="overflow-x-auto">
562 <table className="w-full">
563 <thead className="bg-[var(--brand)] text-surface">
565 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
568 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
571 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
574 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
577 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
580 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
585 <tbody className="bg-surface divide-y divide-border">
586 {filteredStaff.map((member) => (
587 <tr key={member.f100} className="hover:bg-surface-2">
588 <td className="px-6 py-4 whitespace-nowrap text-sm text-text">
591 <td className="px-6 py-4 whitespace-nowrap">
592 <div className="text-sm font-medium text-text">
596 <div className="text-sm text-muted">{member.f121}</div>
599 <td className="px-6 py-4 whitespace-nowrap">
601 className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
603 ? "bg-success/20 text-success"
604 : "bg-danger/20 text-danger"
607 {member.f102 === 1 ? "Active" : "Inactive"}
610 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
613 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
614 {formatDateTime(member.LastOk || "")}
616 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
618 onClick={() => openEditModal(member)}
619 className="text-[#00543b] hover:text-[#003d2b] mr-4"
633 {/* Employees View */}
634 {activeTab === 'employees' && (
636 <div className="bg-surface rounded-lg shadow p-4 mb-6">
637 <div className="flex flex-wrap items-center gap-4">
639 onClick={openNewEmployeeModal}
640 className="bg-success text-white px-6 py-2 rounded-lg hover:bg-success/90 transition-colors flex items-center gap-2"
642 <Icon name="add" size={20} />
646 <div className="flex-1 min-w-[200px] relative">
647 <Icon name="search" size={20} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted" />
650 placeholder="Search employees..."
651 value={employeeSearchTerm}
652 onChange={(e) => setEmployeeSearchTerm(e.target.value)}
653 className="w-full pl-10 pr-4 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-success focus:border-transparent text-text"
659 <div className="bg-surface rounded-lg shadow overflow-hidden">
660 {loadingEmployees ? (
661 <div className="p-8 text-center">
662 <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-success mx-auto"></div>
663 <p className="mt-4 text-muted">Loading employees...</p>
665 ) : filteredEmployees.length === 0 ? (
666 <div className="p-8 text-center text-muted">
667 <Icon name="groups" size={64} className="mx-auto text-muted/50 mb-4" />
668 <p className="text-lg font-semibold mb-2 text-text">No employees found</p>
669 <p className="text-sm">
670 {employeeSearchTerm ? `No employees match "${employeeSearchTerm}"` : "No employee records available"}
674 <div className="overflow-x-auto">
675 <table className="w-full">
676 <thead className="bg-success/10">
678 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">ID</th>
679 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">Short Name</th>
680 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">Full Name</th>
681 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">Job Title</th>
682 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">Department</th>
683 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">Start Date</th>
684 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">External ID</th>
685 <th className="px-6 py-3 text-left text-xs font-medium text-text uppercase tracking-wider">Actions</th>
688 <tbody className="bg-surface divide-y divide-border">
689 {filteredEmployees.map((emp) => (
690 <tr key={emp.f100} className="hover:bg-surface-2">
691 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-text">
694 <td className="px-6 py-4 whitespace-nowrap text-sm text-text">
697 <td className="px-6 py-4 text-sm text-text">
700 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
703 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
706 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
707 {emp.f104 ? new Date(emp.f104).toLocaleDateString() : "—"}
709 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
712 <td className="px-6 py-4 whitespace-nowrap text-sm">
714 onClick={() => openEditEmployeeModal(emp)}
715 className="text-success hover:text-success/80 font-medium flex items-center gap-1"
717 <Icon name="edit" size={16} />
729 <div className="mt-6 bg-success/10 border-l-4 border-success p-4 rounded">
730 <div className="flex items-start gap-3">
731 <Icon name="info" size={20} className="text-success flex-shrink-0 mt-0.5" />
732 <div className="text-sm text-text">
733 <p className="font-semibold mb-1">About Employees</p>
734 <p>Employees are tracked for commission, payroll, and HR purposes. They <strong>do not</strong> have POS login access. If someone needs to operate a cash register, add them as <strong>Staff</strong> instead.</p>
741 {/* Edit/Create Modal */}
743 <div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4 overflow-y-auto">
744 <div className="bg-surface backdrop-blur-md rounded-lg shadow-xl max-w-4xl w-full my-8">
745 <div className="bg-brand backdrop-blur-sm text-white px-6 py-4 rounded-t-lg flex items-center justify-between">
746 <h2 className="text-2xl font-bold text-white flex items-center gap-2">
747 <Icon name="person" size={28} />
748 {editingStaff ? "Edit Staff Member" : "New Staff Member"}
751 onClick={() => setShowModal(false)}
752 className="text-white hover:text-muted/70"
754 <Icon name="close" size={24} />
758 <div className="p-6 max-h-[70vh] overflow-y-auto bg-surface backdrop-blur-md rounded-b-lg">
759 <div className="grid grid-cols-2 gap-6">
761 <label className="block text-sm font-medium text-text mb-1">
766 value={formData.name}
767 onChange={(e) => setFormData({ ...formData, name: e.target.value })}
768 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
774 <label className="block text-sm font-medium text-text mb-1">
779 value={formData.password}
780 onChange={(e) => setFormData({ ...formData, password: e.target.value })}
781 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
782 placeholder={editingStaff ? "Leave blank to keep current" : ""}
787 <label className="flex items-center gap-2 cursor-pointer">
790 checked={formData.active}
791 onChange={(e) => setFormData({ ...formData, active: e.target.checked })}
792 className="w-4 h-4 text-brand rounded"
793 disabled={!editingStaff}
795 <span className="text-sm font-medium text-text">Active</span>
798 <p className="text-xs text-muted mt-1">
799 All staff are created active. Edit after creation to disable.
804 {/* Security Roles - Only show for new staff */}
805 {!editingStaff && securityRoles.length > 0 && (
806 <div className="col-span-2">
807 <label className="block text-sm font-medium text-text mb-2">
808 Assign Security Roles
810 <div className="border border-border rounded-lg p-4 bg-bg max-h-40 overflow-y-auto">
811 <div className="space-y-2">
812 {securityRoles.map((role) => (
813 <label key={role.id} className="flex items-center gap-2 cursor-pointer">
816 checked={selectedRoles.includes(role.id)}
818 if (e.target.checked) {
819 setSelectedRoles([...selectedRoles, role.id]);
821 setSelectedRoles(selectedRoles.filter(id => id !== role.id));
824 className="w-4 h-4 text-brand rounded"
826 <span className="text-sm text-text">{role.name}</span>
834 <div className="col-span-2">
835 <label className="block text-sm font-medium text-text mb-2">
838 <div className="flex flex-wrap gap-4">
839 <label className="flex items-center gap-2 cursor-pointer">
842 checked={(formData.loginWhere & 1) === 0}
847 loginWhere: e.target.checked
848 ? formData.loginWhere & ~bit
849 : formData.loginWhere | bit,
852 className="w-4 h-4 text-brand rounded"
854 <span className="text-sm text-text">POS</span>
856 <label className="flex items-center gap-2 cursor-pointer">
859 checked={(formData.loginWhere & 2) !== 0}
864 loginWhere: e.target.checked
865 ? formData.loginWhere | bit
866 : formData.loginWhere & ~bit,
869 className="w-4 h-4 text-brand rounded"
871 <span className="text-sm text-text">Store Server</span>
873 <label className="flex items-center gap-2 cursor-pointer">
876 checked={(formData.loginWhere & 4) !== 0}
881 loginWhere: e.target.checked
882 ? formData.loginWhere | bit
883 : formData.loginWhere & ~bit,
886 className="w-4 h-4 text-brand rounded"
888 <span className="text-sm text-text">Head Office</span>
891 <p className="text-xs text-muted mt-1">
892 Tick all options for full access user (only effective if security enabled)
897 <label className="block text-sm font-medium text-text mb-1">
902 value={formData.barcode}
903 onChange={(e) => setFormData({ ...formData, barcode: e.target.value })}
904 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
909 <label className="block text-sm font-medium text-text mb-1">
914 value={formData.formalName}
915 onChange={(e) => setFormData({ ...formData, formalName: e.target.value })}
916 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
921 <label className="block text-sm font-medium text-text mb-1">
926 value={formData.printedName}
927 onChange={(e) => setFormData({ ...formData, printedName: e.target.value })}
928 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
933 <label className="block text-sm font-medium text-text mb-1">
938 value={formData.firstName}
939 onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
940 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
945 <label className="block text-sm font-medium text-text mb-1">
950 value={formData.lastName}
951 onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
952 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
957 <label className="block text-sm font-medium text-text mb-1">
962 value={formData.nickName}
963 onChange={(e) => setFormData({ ...formData, nickName: e.target.value })}
964 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
969 <label className="block text-sm font-medium text-text mb-1">
974 value={formData.mainLocation}
975 onChange={(e) => setFormData({ ...formData, mainLocation: e.target.value })}
976 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
977 placeholder="Store number"
979 <p className="text-xs text-muted mt-1">
980 Location number where staff typically works (not enforced)
985 <label className="block text-sm font-medium text-text mb-1">
989 value={formData.remoteProfile}
990 onChange={(e) => setFormData({ ...formData, remoteProfile: parseInt(e.target.value) })}
991 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
993 <option value={0}>None</option>
994 <option value={1}>Profile 1</option>
995 <option value={2}>Profile 2</option>
996 <option value={3}>Profile 3</option>
997 <option value={4}>Profile 4</option>
998 <option value={5}>Profile 5</option>
999 <option value={6}>Profile 6</option>
1000 <option value={7}>Profile 7</option>
1001 <option value={8}>Profile 8</option>
1003 <p className="text-xs text-muted mt-1">
1004 Profile for remote access terminal usage
1009 <label className="block text-sm font-medium text-text mb-1">
1014 value={formData.autoLoginFrom}
1015 onChange={(e) => setFormData({ ...formData, autoLoginFrom: e.target.value })}
1016 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
1017 placeholder="Windows login name"
1019 <p className="text-xs text-muted mt-1">
1020 Auto login to POS if using this Windows login (must be enabled on lane)
1025 <label className="block text-sm font-medium text-text mb-1">
1030 value={formData.workEmail}
1031 onChange={(e) => setFormData({ ...formData, workEmail: e.target.value })}
1032 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
1037 <label className="block text-sm font-medium text-text mb-1">
1042 value={formData.privateEmail}
1043 onChange={(e) => setFormData({ ...formData, privateEmail: e.target.value })}
1044 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
1049 <label className="block text-sm font-medium text-text mb-1">
1054 value={formData.onlineLogin}
1055 onChange={(e) => setFormData({ ...formData, onlineLogin: e.target.value })}
1056 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
1062 <label className="block text-sm font-medium text-text mb-1">
1067 value={formData.onlinePassword}
1068 onChange={(e) => setFormData({ ...formData, onlinePassword: e.target.value })}
1069 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
1075 <label className="block text-sm font-medium text-text mb-1">
1080 value={formData.startDate}
1081 onChange={(e) => setFormData({ ...formData, startDate: e.target.value })}
1082 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
1087 <label className="block text-sm font-medium text-text mb-1">
1092 value={formData.leaveDate}
1093 onChange={(e) => setFormData({ ...formData, leaveDate: e.target.value })}
1094 className="w-full px-3 py-2 bg-bg border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent text-text"
1098 <div className="col-span-2">
1099 <label className="block text-sm font-medium text-text mb-2">
1102 <div className="flex gap-6">
1103 <label className="flex items-center gap-2 cursor-pointer">
1106 checked={formData.isCustomerRep}
1107 onChange={(e) => setFormData({ ...formData, isCustomerRep: e.target.checked })}
1108 className="w-4 h-4 text-brand rounded"
1110 <span className="text-sm text-text">Is Customer Rep</span>
1112 <label className="flex items-center gap-2 cursor-pointer">
1115 checked={formData.isAccountRep}
1116 onChange={(e) => setFormData({ ...formData, isAccountRep: e.target.checked })}
1117 className="w-4 h-4 text-brand rounded"
1119 <span className="text-sm text-text">Is Sales/Account Rep</span>
1126 <div className="bg-surface-2 px-6 py-4 rounded-b-lg flex justify-end gap-3">
1128 onClick={() => setShowModal(false)}
1129 className="px-6 py-2 border border-border rounded-lg hover:bg-surface transition-colors text-text"
1134 onClick={handleSave}
1135 className="bg-brand text-white px-6 py-2 rounded-lg hover:bg-brand/90 transition-colors"
1137 {editingStaff ? "Save Changes" : "Create Staff Member"}
1144 {/* Employee Edit/Create Modal */}
1145 {showEmployeeModal && (
1146 <div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4 overflow-y-auto">
1147 <div className="bg-surface backdrop-blur-md rounded-lg shadow-xl max-w-3xl w-full my-8">
1148 <div className="bg-success backdrop-blur-sm text-white px-6 py-4 rounded-t-lg flex items-center justify-between">
1149 <h2 className="text-2xl font-bold text-white flex items-center gap-2">
1150 <Icon name="groups" size={28} />
1151 {editingEmployee ? "Edit Employee" : "New Employee"}
1155 setShowEmployeeModal(false);
1156 setEditingEmployee(null);
1158 className="text-white hover:text-muted/70"
1160 <Icon name="close" size={24} />
1164 <div className="p-6 max-h-[70vh] overflow-y-auto bg-surface backdrop-blur-md">
1165 <div className="grid grid-cols-2 gap-6">
1167 <label className="block text-sm font-medium text-text mb-1">
1168 Short Name <span className="text-danger">*</span>
1172 value={editingEmployee?.f101 || ""}
1173 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f101: e.target.value })}
1174 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1175 placeholder="Short display name"
1180 <label className="block text-sm font-medium text-text mb-1">
1181 Full Name <span className="text-danger">*</span>
1185 value={editingEmployee?.f102 || ""}
1186 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f102: e.target.value })}
1187 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1188 placeholder="Full legal name"
1193 <label className="block text-sm font-medium text-text mb-1">
1198 value={editingEmployee?.f103 || ""}
1199 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f103: e.target.value })}
1200 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1201 placeholder="Optional POS login reference"
1206 <label className="block text-sm font-medium text-text mb-1">
1211 value={editingEmployee?.f107 || ""}
1212 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f107: e.target.value })}
1213 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1214 placeholder="e.g. Sales Associate"
1219 <label className="block text-sm font-medium text-text mb-1">
1224 value={editingEmployee?.f108 || ""}
1225 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f108: e.target.value })}
1226 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1227 placeholder="Internal job code"
1232 <label className="block text-sm font-medium text-text mb-1">
1237 value={editingEmployee?.f109 || ""}
1238 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f109: e.target.value })}
1239 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1240 placeholder="Department identifier"
1245 <label className="block text-sm font-medium text-text mb-1">
1250 value={editingEmployee?.f106 || ""}
1251 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f106: e.target.value })}
1252 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1253 placeholder="Primary work location"
1258 <label className="block text-sm font-medium text-text mb-1">
1263 value={editingEmployee?.f110 || ""}
1264 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f110: e.target.value })}
1265 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1266 placeholder="External system ID"
1271 <label className="block text-sm font-medium text-text mb-1">
1276 value={editingEmployee?.f104 || ""}
1277 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f104: e.target.value })}
1278 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1283 <label className="block text-sm font-medium text-text mb-1">
1288 value={editingEmployee?.f105 || ""}
1289 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f105: e.target.value })}
1290 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1295 <label className="block text-sm font-medium text-text mb-1">
1300 value={editingEmployee?.f114 || ""}
1301 onChange={(e) => setEditingEmployee({ ...editingEmployee!, f114: e.target.value })}
1302 className="w-full px-3 py-2 bg-bg border border-border rounded focus:ring-2 focus:ring-success focus:border-transparent text-text"
1303 placeholder="NZ Transport Agency ID"
1309 <div className="bg-surface-2 px-6 py-4 rounded-b-lg flex justify-end gap-3">
1312 setShowEmployeeModal(false);
1313 setEditingEmployee(null);
1315 className="px-6 py-2 border border-border rounded-lg hover:bg-surface transition-colors text-text"
1320 onClick={handleSaveEmployee}
1321 className="bg-success text-white px-6 py-2 rounded-lg hover:bg-success/90 transition-colors"
1322 disabled={!editingEmployee?.f101 || !editingEmployee?.f102}
1324 {editingEmployee?.f100 ? "Save Changes" : "Create Employee"}