3import { useEffect, useState } from "react";
4import { apiClient } from "@/lib/client/apiClient";
8 f101: string; // Short Name
9 f102: string; // Full Name
10 f103?: string; // POS Login ID
11 f104?: string; // Start Date
12 f105?: string; // End Date
13 f106?: string; // Main Location
14 f107?: string; // Job Title
15 f108?: string; // Job Code
16 f109?: string; // Department ID
17 f110?: string; // External ID
18 f114?: string; // NZTA UID
21interface EmployeeFormData {
36export default function EmployeesPage() {
37 const [employees, setEmployees] = useState<Employee[]>([]);
38 const [filteredEmployees, setFilteredEmployees] = useState<Employee[]>([]);
39 const [loading, setLoading] = useState(true);
40 const [searchTerm, setSearchTerm] = useState("");
41 const [showModal, setShowModal] = useState(false);
42 const [editingEmployee, setEditingEmployee] = useState<Employee | null>(null);
43 const [formData, setFormData] = useState<EmployeeFormData>({
63 }, [employees, searchTerm]);
65 const loadEmployees = async () => {
68 const result = await apiClient.getEmployees();
70 if (result.success && result.data?.DATS) {
71 setEmployees(result.data.DATS);
74 console.error("Error loading employees:", error);
80 const applyFilters = () => {
82 setFilteredEmployees(employees);
86 const search = searchTerm.toLowerCase();
87 const filtered = employees.filter((emp) => {
89 emp.f100?.toString().includes(search) ||
90 emp.f101?.toLowerCase().includes(search) ||
91 emp.f102?.toLowerCase().includes(search) ||
92 emp.f107?.toLowerCase().includes(search) ||
93 emp.f110?.toLowerCase().includes(search)
97 setFilteredEmployees(filtered);
100 const openNewEmployeeModal = () => {
101 setEditingEmployee(null);
118 const openEditModal = (employee: Employee) => {
119 setEditingEmployee(employee);
122 shortName: employee.f101 || "",
123 fullName: employee.f102 || "",
124 posLoginId: employee.f103 || "",
125 startDate: employee.f104 || "",
126 endDate: employee.f105 || "",
127 mainLocation: employee.f106 || "",
128 jobTitle: employee.f107 || "",
129 jobCode: employee.f108 || "",
130 departmentId: employee.f109 || "",
131 externalId: employee.f110 || "",
132 nztaUid: employee.f114 || "",
137 const handleSave = async () => {
139 const result = await apiClient.saveEmployee(formData);
141 if (result.success) {
145 alert("Failed to save employee: " + (result.error || "Unknown error"));
148 console.error("Error saving employee:", error);
149 alert("Failed to save employee");
153 const formatDate = (dateStr: string) => {
154 if (!dateStr) return "—";
155 const date = new Date(dateStr);
156 return date.toLocaleDateString("en-GB", {
165 <div className="p-6 bg-surface-2 min-h-screen">
166 <div className="animate-pulse space-y-4">
167 <div className="h-8 bg-surface-2 rounded w-1/4"></div>
168 <div className="h-64 bg-surface-2 rounded"></div>
175 <div className="p-6 bg-surface-2 min-h-screen">
177 <div className="mb-6">
178 <h1 className="text-3xl font-bold text-text mb-2">
181 <p className="text-muted">
182 Employees are people who work for you and are known to the POS, but do not
183 necessarily have access to the Point of Sale. Often used for commission tracking
184 for non-retail staff.
189 <div className="bg-surface rounded-lg shadow p-4 mb-6">
190 <div className="flex flex-wrap items-center gap-4">
192 onClick={openNewEmployeeModal}
193 className="bg-[#00543b] text-white px-6 py-2 rounded-lg hover:bg-[#003d2b] transition-colors flex items-center gap-2"
195 <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
196 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
201 <div className="flex-1 min-w-[200px]">
204 placeholder="Search employees..."
206 onChange={(e) => setSearchTerm(e.target.value)}
207 className="w-full px-4 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
212 <div className="mt-2 text-sm text-muted">
213 Displaying {filteredEmployees.length} of {employees.length} employees
217 {/* Employees Table */}
218 <div className="bg-surface rounded-lg shadow overflow-hidden">
219 <div className="overflow-x-auto">
220 <table className="w-full">
221 <thead className="bg-[var(--brand)] text-surface">
223 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
226 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
229 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
232 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
235 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
238 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
241 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
246 <tbody className="bg-surface divide-y divide-gray-200">
247 {filteredEmployees.map((employee) => (
248 <tr key={employee.f100} className="hover:bg-surface-2">
249 <td className="px-6 py-4 whitespace-nowrap text-sm text-text">
252 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-text">
255 <td className="px-6 py-4 whitespace-nowrap text-sm text-text">
258 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
259 {employee.f107 || "—"}
261 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
262 {formatDate(employee.f104 || "")}
264 <td className="px-6 py-4 whitespace-nowrap text-sm text-muted">
265 {formatDate(employee.f105 || "")}
267 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
269 onClick={() => openEditModal(employee)}
270 className="text-[#00543b] hover:text-[#003d2b]"
282 {/* Edit/Create Modal */}
284 <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4 overflow-y-auto">
285 <div className="bg-surface rounded-lg shadow-xl max-w-3xl w-full my-8">
286 <div className="bg-[#00543b] text-white px-6 py-4 rounded-t-lg flex items-center justify-between">
287 <h2 className="text-2xl font-bold">
288 {editingEmployee ? "Edit Employee" : "New Employee"}
291 onClick={() => setShowModal(false)}
292 className="text-white hover:text-muted/70"
294 <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
295 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
300 <div className="p-6 max-h-[70vh] overflow-y-auto">
301 <div className="grid grid-cols-2 gap-6">
303 <label className="block text-sm font-medium text-text mb-1">
308 value={formData.shortName}
309 onChange={(e) => setFormData({ ...formData, shortName: e.target.value })}
310 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
316 <label className="block text-sm font-medium text-text mb-1">
321 value={formData.fullName}
322 onChange={(e) => setFormData({ ...formData, fullName: e.target.value })}
323 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
329 <label className="block text-sm font-medium text-text mb-1">
334 value={formData.posLoginId}
335 onChange={(e) => setFormData({ ...formData, posLoginId: e.target.value })}
336 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
338 <p className="text-xs text-muted mt-1">Links to staff login account</p>
342 <label className="block text-sm font-medium text-text mb-1">
347 value={formData.externalId}
348 onChange={(e) => setFormData({ ...formData, externalId: e.target.value })}
349 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
351 <p className="text-xs text-muted mt-1">External system reference</p>
355 <label className="block text-sm font-medium text-text mb-1">
360 value={formData.startDate}
361 onChange={(e) => setFormData({ ...formData, startDate: e.target.value })}
362 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
367 <label className="block text-sm font-medium text-text mb-1">
372 value={formData.endDate}
373 onChange={(e) => setFormData({ ...formData, endDate: e.target.value })}
374 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
379 <label className="block text-sm font-medium text-text mb-1">
384 value={formData.mainLocation}
385 onChange={(e) => setFormData({ ...formData, mainLocation: e.target.value })}
386 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
387 placeholder="Store number"
392 <label className="block text-sm font-medium text-text mb-1">
397 value={formData.departmentId}
398 onChange={(e) => setFormData({ ...formData, departmentId: e.target.value })}
399 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
404 <label className="block text-sm font-medium text-text mb-1">
409 value={formData.jobTitle}
410 onChange={(e) => setFormData({ ...formData, jobTitle: e.target.value })}
411 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
416 <label className="block text-sm font-medium text-text mb-1">
421 value={formData.jobCode}
422 onChange={(e) => setFormData({ ...formData, jobCode: e.target.value })}
423 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
428 <label className="block text-sm font-medium text-text mb-1">
433 value={formData.nztaUid}
434 onChange={(e) => setFormData({ ...formData, nztaUid: e.target.value })}
435 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[#00543b] focus:border-transparent"
441 <div className="bg-surface-2 px-6 py-4 rounded-b-lg flex justify-end gap-3">
443 onClick={() => setShowModal(false)}
444 className="px-6 py-2 border border-border rounded-lg hover:bg-surface-2 transition-colors"
450 className="bg-[#00543b] text-white px-6 py-2 rounded-lg hover:bg-[#003d2b] transition-colors"
452 {editingEmployee ? "Save Changes" : "Create Employee"}