feat: Implement Edit Staff Functionality For Administrators
This commit is contained in:
@@ -125,7 +125,6 @@ const Login: React.FC = () => {
|
||||
// Dispatch Redux action to handle login
|
||||
dispatch(loginUser({ email, password }));
|
||||
};
|
||||
console.log(user);
|
||||
return (
|
||||
<div className="flex min-h-screen bg-slate-50/50">
|
||||
{/* Left Side: Hero Image (Hidden on Mobile) */}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
const AdminDashboard = () => {
|
||||
return (
|
||||
<div>Dashboard</div>
|
||||
<div> Admin Dashboard</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "@/common/components/ui/button";
|
||||
import { Input } from "@/common/components/ui/input";
|
||||
import { Label } from "@/common/components/ui/label";
|
||||
import { ArrowLeft, Loader2, Save, Mail, Phone, User, Award, ShieldAlert } from "lucide-react";
|
||||
import { ArrowLeft, Loader2, Save, Mail, Phone, User, Award } from "lucide-react";
|
||||
import DashboardLayout from "@/features/layouts/DashboardLayout";
|
||||
import { useCreateStaff, useGetUserById } from "@/dataconnect-generated/react";
|
||||
import { dataConnect, auth } from "@/features/auth/firebase";
|
||||
import { useCreateStaff } from "@/dataconnect-generated/react";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { Checkbox } from "@/common/components/ui/checkbox";
|
||||
import { BackgroundCheckStatus } from "@/dataconnect-generated";
|
||||
|
||||
const COMMON_SKILLS = [
|
||||
"Barista",
|
||||
@@ -30,15 +31,6 @@ interface AddStaffFormData {
|
||||
export default function AddStaff() {
|
||||
const navigate = useNavigate();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Get current user and their role from DataConnect
|
||||
const currentUser = auth.currentUser;
|
||||
const { data: userData, isLoading: isUserLoading } = useGetUserById(
|
||||
dataConnect,
|
||||
{ id: currentUser?.uid || "" },
|
||||
{ enabled: !!currentUser }
|
||||
);
|
||||
|
||||
const { mutateAsync: createStaff } = useCreateStaff(dataConnect);
|
||||
|
||||
const { register, handleSubmit, control, formState: { errors } } = useForm<AddStaffFormData>({
|
||||
@@ -51,22 +43,7 @@ export default function AddStaff() {
|
||||
}
|
||||
});
|
||||
|
||||
// Check for admin role
|
||||
const isAdmin = userData?.user?.userRole?.toLowerCase() === 'admin';
|
||||
|
||||
useEffect(() => {
|
||||
if (!isUserLoading && !isAdmin && currentUser) {
|
||||
// Small delay to allow user to see why they are being redirected if needed,
|
||||
// but usually immediate is better for security
|
||||
const timer = setTimeout(() => {
|
||||
navigate("/staff");
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isAdmin, isUserLoading, navigate, currentUser]);
|
||||
|
||||
const onSubmit = async (data: AddStaffFormData) => {
|
||||
if (!isAdmin) return;
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await createStaff({
|
||||
@@ -75,7 +52,7 @@ export default function AddStaff() {
|
||||
email: data.email,
|
||||
phone: data.phone,
|
||||
skills: data.skills,
|
||||
backgroundCheckStatus: "PENDING",
|
||||
backgroundCheckStatus: BackgroundCheckStatus.PENDING,
|
||||
initial: `${data.firstName.charAt(0)}${data.lastName.charAt(0)}`.toUpperCase(),
|
||||
});
|
||||
navigate("/staff");
|
||||
@@ -86,39 +63,6 @@ export default function AddStaff() {
|
||||
}
|
||||
};
|
||||
|
||||
if (isUserLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen gap-4">
|
||||
<Loader2 className="w-10 h-10 animate-spin text-primary" />
|
||||
<p className="text-muted-foreground font-bold animate-pulse">VERIFYING PERMISSIONS...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen gap-6 p-4 text-center">
|
||||
<div className="w-20 h-20 bg-rose-500/10 rounded-3xl flex items-center justify-center border-2 border-rose-500/20 shadow-xl shadow-rose-500/5">
|
||||
<ShieldAlert className="w-10 h-10 text-rose-500" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-black text-foreground">Access Denied</h2>
|
||||
<p className="text-muted-foreground font-medium max-w-md">
|
||||
Only administrators can manually onboard new staff members.
|
||||
You are being redirected to the staff directory.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate("/staff")}
|
||||
className="rounded-xl font-bold px-8"
|
||||
>
|
||||
Return Now
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
title="Add New Staff"
|
||||
@@ -220,8 +164,7 @@ export default function AddStaff() {
|
||||
<Checkbox
|
||||
id={skill}
|
||||
checked={field.value?.includes(skill)}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
onCheckedChange={(checked) => {
|
||||
const updatedSkills = checked
|
||||
? [...(field.value || []), skill]
|
||||
: field.value?.filter((s: string) => s !== skill);
|
||||
@@ -262,7 +205,6 @@ export default function AddStaff() {
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>= </DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/store/store";
|
||||
import { Button } from "@/common/components/ui/button";
|
||||
import { ArrowLeft, Loader2, Edit, Save, X, User, FileText, Briefcase, Shield } from "lucide-react";
|
||||
import { ArrowLeft, Loader2, Edit, Save, X, AlertCircle } from "lucide-react";
|
||||
import StaffForm from "./components/StaffForm";
|
||||
import DashboardLayout from "@/features/layouts/DashboardLayout";
|
||||
import { useGetStaffById, useUpdateStaff } from "@/dataconnect-generated/react";
|
||||
import { useGetStaffById, useUpdateStaff, useCreateActivityLog } from "@/dataconnect-generated/react";
|
||||
import { dataConnect } from "@/features/auth/firebase";
|
||||
import type { Staff } from "../type";
|
||||
import { Badge } from "@/common/components/ui/badge";
|
||||
import { ActivityIconType, ActivityType } from "@/dataconnect-generated";
|
||||
|
||||
export default function EditStaff() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const { data: staffData, isLoading, refetch } = useGetStaffById(dataConnect, { id: id || "" });
|
||||
const { mutateAsync: updateStaff } = useUpdateStaff(dataConnect);
|
||||
const { mutateAsync: createActivityLog } = useCreateActivityLog(dataConnect);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const isAdmin = user?.userRole === 'admin' || user?.userRole === 'ADMIN';
|
||||
|
||||
const staff = useMemo(() => {
|
||||
if (!staffData?.staff) return null;
|
||||
const s = staffData.staff;
|
||||
@@ -48,7 +55,7 @@ export default function EditStaff() {
|
||||
}, [staffData]);
|
||||
|
||||
const handleSubmit = async (staffData: Staff) => {
|
||||
if (!id) return;
|
||||
if (!id || !isAdmin) return;
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await updateStaff({
|
||||
@@ -69,6 +76,18 @@ export default function EditStaff() {
|
||||
city: staffData.city,
|
||||
addres: staffData.address,
|
||||
});
|
||||
|
||||
// Audit Log
|
||||
await createActivityLog({
|
||||
userId: user?.uid || "system",
|
||||
date: new Date().toISOString(),
|
||||
title: "Staff Profile Updated",
|
||||
description: `Administrator ${user?.displayName || user?.email} updated professional records for ${staffData.employee_name}`,
|
||||
activityType: ActivityType.SYSTEM_UPDATE,
|
||||
iconType: ActivityIconType.CHECK,
|
||||
iconColor: "emerald",
|
||||
});
|
||||
|
||||
await refetch();
|
||||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
@@ -91,6 +110,27 @@ export default function EditStaff() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<DashboardLayout title="Access Denied" subtitle="Unauthorized Access">
|
||||
<div className="flex flex-col items-center justify-center min-h-[40vh] gap-6 bg-card/60 backdrop-blur-md border border-border rounded-3xl p-12 text-center">
|
||||
<div className="w-20 h-20 bg-rose-500/10 rounded-full flex items-center justify-center text-rose-500 border border-rose-500/20 shadow-lg">
|
||||
<AlertCircle className="w-10 h-10" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-black text-foreground">Restricted Access</h2>
|
||||
<p className="text-muted-foreground max-w-md mx-auto">
|
||||
Only administrators are authorized to modify staff professional records. Please contact your system administrator if you believe this is an error.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => navigate("/staff")} variant="outline" leadingIcon={<ArrowLeft />} className="rounded-xl font-bold">
|
||||
Return to Directory
|
||||
</Button>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
title={isEditing ? `Edit: ${staff?.employee_name || 'Staff Member'}` : staff?.employee_name || 'Staff Member'}
|
||||
|
||||
Reference in New Issue
Block a user