feat: Implement Staff View
This commit is contained in:
@@ -4,17 +4,36 @@ import { Button } from "@/common/components/ui/button";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import StaffForm from "./components/StaffForm";
|
||||
import DashboardLayout from "@/features/layouts/DashboardLayout";
|
||||
import { workforceService } from "@/services/workforceService";
|
||||
import { useCreateStaff } from "@/dataconnect-generated/react";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import type { Staff } from "../type";
|
||||
|
||||
export default function AddStaff() {
|
||||
const navigate = useNavigate();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const { mutateAsync: createStaff } = useCreateStaff(dataConnect);
|
||||
|
||||
const handleSubmit = async (staffData: Omit<Staff, 'id'>) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await workforceService.entities.Staff.create(staffData);
|
||||
await createStaff({
|
||||
userId: `user_${Math.random().toString(36).substr(2, 9)}`, // Placeholder UID
|
||||
fullName: staffData.employee_name,
|
||||
role: staffData.position,
|
||||
level: staffData.profile_type,
|
||||
email: staffData.email,
|
||||
phone: staffData.contact_number,
|
||||
photoUrl: staffData.photo,
|
||||
initial: staffData.initial,
|
||||
bio: staffData.notes,
|
||||
skills: staffData.skills,
|
||||
averageRating: staffData.averageRating,
|
||||
reliabilityScore: staffData.reliability_score,
|
||||
onTimeRate: staffData.shift_coverage_percentage,
|
||||
totalShifts: staffData.total_shifts,
|
||||
city: staffData.city,
|
||||
addres: staffData.address,
|
||||
});
|
||||
navigate("/staff");
|
||||
} catch (error) {
|
||||
console.error("Failed to create staff", error);
|
||||
|
||||
@@ -1,48 +1,75 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { Button } from "@/common/components/ui/button";
|
||||
import { ArrowLeft, Loader2 } from "lucide-react";
|
||||
import { ArrowLeft, Loader2, Edit, Save, X, User, FileText, Briefcase, Shield } from "lucide-react";
|
||||
import StaffForm from "./components/StaffForm";
|
||||
import DashboardLayout from "@/features/layouts/DashboardLayout";
|
||||
import { workforceService } from "@/services/workforceService";
|
||||
import { useGetStaffById, useUpdateStaff } from "@/dataconnect-generated/react";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import type { Staff } from "../type";
|
||||
import { Badge } from "@/common/components/ui/badge";
|
||||
|
||||
export default function EditStaff() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [staff, setStaff] = useState<Staff | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStaff = async () => {
|
||||
if (!id) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const staffList = await workforceService.entities.Staff.list();
|
||||
const foundStaff = staffList.find(s => s.id === id);
|
||||
if (foundStaff) {
|
||||
setStaff(foundStaff);
|
||||
} else {
|
||||
console.error("Staff member not found");
|
||||
navigate("/staff");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch staff", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchStaff();
|
||||
}, [id, navigate]);
|
||||
const { data: staffData, isLoading, refetch } = useGetStaffById(dataConnect, { id: id || "" });
|
||||
const { mutateAsync: updateStaff } = useUpdateStaff(dataConnect);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const staff = useMemo(() => {
|
||||
if (!staffData?.staff) return null;
|
||||
const s = staffData.staff;
|
||||
return {
|
||||
id: s.id,
|
||||
employee_name: s.fullName,
|
||||
initial: s.initial || "",
|
||||
position: s.role || "",
|
||||
position_2: "",
|
||||
profile_type: s.level || "",
|
||||
employment_type: s.employmentType || "",
|
||||
manager: s.manager || "",
|
||||
email: s.email || "",
|
||||
contact_number: s.phone || "",
|
||||
phone: s.phone || "",
|
||||
photo: s.photoUrl || "",
|
||||
status: "Active", // Default for now
|
||||
skills: (s.skills as string[]) || [],
|
||||
averageRating: s.averageRating || 0,
|
||||
reliability_score: s.reliabilityScore || 0,
|
||||
shift_coverage_percentage: s.onTimeRate || 0,
|
||||
total_shifts: s.totalShifts || 0,
|
||||
department: s.department || "",
|
||||
city: s.city || "",
|
||||
address: s.addres || "", // Using misspelled field from schema
|
||||
notes: s.bio || "",
|
||||
} as Staff;
|
||||
}, [staffData]);
|
||||
|
||||
const handleSubmit = async (staffData: Staff) => {
|
||||
if (!id) return;
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await workforceService.entities.Staff.update(id, staffData);
|
||||
setStaff(staffData);
|
||||
await updateStaff({
|
||||
id: id,
|
||||
fullName: staffData.employee_name,
|
||||
role: staffData.position,
|
||||
level: staffData.profile_type,
|
||||
email: staffData.email,
|
||||
phone: staffData.contact_number,
|
||||
photoUrl: staffData.photo,
|
||||
initial: staffData.initial,
|
||||
bio: staffData.notes,
|
||||
skills: staffData.skills,
|
||||
averageRating: staffData.averageRating,
|
||||
reliabilityScore: staffData.reliability_score,
|
||||
onTimeRate: staffData.shift_coverage_percentage,
|
||||
totalShifts: staffData.total_shifts,
|
||||
city: staffData.city,
|
||||
addres: staffData.address,
|
||||
});
|
||||
await refetch();
|
||||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to update staff", error);
|
||||
@@ -67,7 +94,7 @@ export default function EditStaff() {
|
||||
return (
|
||||
<DashboardLayout
|
||||
title={isEditing ? `Edit: ${staff?.employee_name || 'Staff Member'}` : staff?.employee_name || 'Staff Member'}
|
||||
subtitle={`Management of ${staff?.employee_name}'s professional records`}
|
||||
subtitle={isEditing ? `Modifying ${staff?.employee_name}'s professional records` : `Management of ${staff?.employee_name}'s professional records`}
|
||||
backAction={
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -80,35 +107,72 @@ export default function EditStaff() {
|
||||
}
|
||||
>
|
||||
{staff && (
|
||||
<div>
|
||||
{!isEditing && (
|
||||
<div className="mb-6 flex justify-end">
|
||||
<Button onClick={() => setIsEditing(true)} variant="secondary">
|
||||
Edit
|
||||
</Button>
|
||||
<div className="space-y-6">
|
||||
{/* Profile Header */}
|
||||
<div className="bg-card/60 backdrop-blur-md border border-border p-6 rounded-3xl flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<div className="flex flex-col md:flex-row items-center gap-6 text-center md:text-left">
|
||||
<div className="w-24 h-24 bg-primary/10 rounded-2xl flex items-center justify-center text-primary font-black text-3xl border-2 border-primary/20 shadow-xl overflow-hidden group">
|
||||
{staff.photo ? (
|
||||
<img src={staff.photo} alt={staff.employee_name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
staff.employee_name.charAt(0)
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-black text-foreground mb-1">{staff.employee_name}</h2>
|
||||
<div className="flex flex-wrap items-center justify-center md:justify-start gap-2">
|
||||
<Badge className="bg-emerald-500/10 text-emerald-600 border-emerald-500/20 font-black text-xs uppercase px-3 py-1">
|
||||
{staff.status || 'Active'}
|
||||
</Badge>
|
||||
<span className="text-muted-foreground font-bold text-sm">•</span>
|
||||
<span className="text-muted-foreground font-bold text-sm">{staff.position || 'No Role Assigned'}</span>
|
||||
{staff.profile_type && (
|
||||
<>
|
||||
<span className="text-muted-foreground font-bold text-sm">•</span>
|
||||
<span className="text-primary font-bold text-sm">{staff.profile_type}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isEditing && (
|
||||
<div className="mb-6 flex justify-end gap-2">
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
variant="outline"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// Trigger form submission by dispatching event or calling form submit
|
||||
const form = document.querySelector('form');
|
||||
if (form) form.requestSubmit();
|
||||
}}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
|
||||
<div className="flex gap-3">
|
||||
{!isEditing ? (
|
||||
<Button
|
||||
onClick={() => setIsEditing(true)}
|
||||
variant="secondary"
|
||||
leadingIcon={<Edit className="w-4 h-4" />}
|
||||
className="rounded-xl px-6 font-bold"
|
||||
>
|
||||
Edit Profile
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
variant="outline"
|
||||
disabled={isSubmitting}
|
||||
leadingIcon={<X className="w-4 h-4" />}
|
||||
className="rounded-xl font-bold"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const form = document.querySelector('form');
|
||||
if (form) form.requestSubmit();
|
||||
}}
|
||||
disabled={isSubmitting}
|
||||
leadingIcon={isSubmitting ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
||||
className="rounded-xl px-6 font-bold"
|
||||
>
|
||||
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<StaffForm
|
||||
staff={staff}
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
Reference in New Issue
Block a user