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 Redux action to handle login
|
||||||
dispatch(loginUser({ email, password }));
|
dispatch(loginUser({ email, password }));
|
||||||
};
|
};
|
||||||
console.log(user);
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen bg-slate-50/50">
|
<div className="flex min-h-screen bg-slate-50/50">
|
||||||
{/* Left Side: Hero Image (Hidden on Mobile) */}
|
{/* Left Side: Hero Image (Hidden on Mobile) */}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
const AdminDashboard = () => {
|
const AdminDashboard = () => {
|
||||||
return (
|
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 { useNavigate } from "react-router-dom";
|
||||||
import { Button } from "@/common/components/ui/button";
|
import { Button } from "@/common/components/ui/button";
|
||||||
import { Input } from "@/common/components/ui/input";
|
import { Input } from "@/common/components/ui/input";
|
||||||
import { Label } from "@/common/components/ui/label";
|
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 DashboardLayout from "@/features/layouts/DashboardLayout";
|
||||||
import { useCreateStaff, useGetUserById } from "@/dataconnect-generated/react";
|
import { useCreateStaff } from "@/dataconnect-generated/react";
|
||||||
import { dataConnect, auth } from "@/features/auth/firebase";
|
import { dataConnect } from "@/features/auth/firebase";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import { Checkbox } from "@/common/components/ui/checkbox";
|
import { Checkbox } from "@/common/components/ui/checkbox";
|
||||||
|
import { BackgroundCheckStatus } from "@/dataconnect-generated";
|
||||||
|
|
||||||
const COMMON_SKILLS = [
|
const COMMON_SKILLS = [
|
||||||
"Barista",
|
"Barista",
|
||||||
@@ -30,15 +31,6 @@ interface AddStaffFormData {
|
|||||||
export default function AddStaff() {
|
export default function AddStaff() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
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 { mutateAsync: createStaff } = useCreateStaff(dataConnect);
|
||||||
|
|
||||||
const { register, handleSubmit, control, formState: { errors } } = useForm<AddStaffFormData>({
|
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) => {
|
const onSubmit = async (data: AddStaffFormData) => {
|
||||||
if (!isAdmin) return;
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await createStaff({
|
await createStaff({
|
||||||
@@ -75,7 +52,7 @@ export default function AddStaff() {
|
|||||||
email: data.email,
|
email: data.email,
|
||||||
phone: data.phone,
|
phone: data.phone,
|
||||||
skills: data.skills,
|
skills: data.skills,
|
||||||
backgroundCheckStatus: "PENDING",
|
backgroundCheckStatus: BackgroundCheckStatus.PENDING,
|
||||||
initial: `${data.firstName.charAt(0)}${data.lastName.charAt(0)}`.toUpperCase(),
|
initial: `${data.firstName.charAt(0)}${data.lastName.charAt(0)}`.toUpperCase(),
|
||||||
});
|
});
|
||||||
navigate("/staff");
|
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 (
|
return (
|
||||||
<DashboardLayout
|
<DashboardLayout
|
||||||
title="Add New Staff"
|
title="Add New Staff"
|
||||||
@@ -220,8 +164,7 @@ export default function AddStaff() {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id={skill}
|
id={skill}
|
||||||
checked={field.value?.includes(skill)}
|
checked={field.value?.includes(skill)}
|
||||||
onChange={(e) => {
|
onCheckedChange={(checked) => {
|
||||||
const checked = e.target.checked;
|
|
||||||
const updatedSkills = checked
|
const updatedSkills = checked
|
||||||
? [...(field.value || []), skill]
|
? [...(field.value || []), skill]
|
||||||
: field.value?.filter((s: string) => s !== skill);
|
: field.value?.filter((s: string) => s !== skill);
|
||||||
@@ -262,7 +205,6 @@ export default function AddStaff() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>= </DashboardLayout>
|
||||||
</DashboardLayout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
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 { 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 StaffForm from "./components/StaffForm";
|
||||||
import DashboardLayout from "@/features/layouts/DashboardLayout";
|
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 { dataConnect } from "@/features/auth/firebase";
|
||||||
import type { Staff } from "../type";
|
import type { Staff } from "../type";
|
||||||
import { Badge } from "@/common/components/ui/badge";
|
import { Badge } from "@/common/components/ui/badge";
|
||||||
|
import { ActivityIconType, ActivityType } from "@/dataconnect-generated";
|
||||||
|
|
||||||
export default function EditStaff() {
|
export default function EditStaff() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const { user } = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const { data: staffData, isLoading, refetch } = useGetStaffById(dataConnect, { id: id || "" });
|
const { data: staffData, isLoading, refetch } = useGetStaffById(dataConnect, { id: id || "" });
|
||||||
const { mutateAsync: updateStaff } = useUpdateStaff(dataConnect);
|
const { mutateAsync: updateStaff } = useUpdateStaff(dataConnect);
|
||||||
|
const { mutateAsync: createActivityLog } = useCreateActivityLog(dataConnect);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const isAdmin = user?.userRole === 'admin' || user?.userRole === 'ADMIN';
|
||||||
|
|
||||||
const staff = useMemo(() => {
|
const staff = useMemo(() => {
|
||||||
if (!staffData?.staff) return null;
|
if (!staffData?.staff) return null;
|
||||||
const s = staffData.staff;
|
const s = staffData.staff;
|
||||||
@@ -48,7 +55,7 @@ export default function EditStaff() {
|
|||||||
}, [staffData]);
|
}, [staffData]);
|
||||||
|
|
||||||
const handleSubmit = async (staffData: Staff) => {
|
const handleSubmit = async (staffData: Staff) => {
|
||||||
if (!id) return;
|
if (!id || !isAdmin) return;
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await updateStaff({
|
await updateStaff({
|
||||||
@@ -69,6 +76,18 @@ export default function EditStaff() {
|
|||||||
city: staffData.city,
|
city: staffData.city,
|
||||||
addres: staffData.address,
|
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();
|
await refetch();
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
} catch (error) {
|
} 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 (
|
return (
|
||||||
<DashboardLayout
|
<DashboardLayout
|
||||||
title={isEditing ? `Edit: ${staff?.employee_name || 'Staff Member'}` : staff?.employee_name || 'Staff Member'}
|
title={isEditing ? `Edit: ${staff?.employee_name || 'Staff Member'}` : staff?.employee_name || 'Staff Member'}
|
||||||
|
|||||||
Reference in New Issue
Block a user