feat: Implement Staff Detail View
This commit is contained in:
@@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import AppRoutes from './routes';
|
||||
import { store } from './store/store';
|
||||
import { initializeAuthPersistence } from './services/authService';
|
||||
import AuthInitializer from './features/auth/AuthInitializer';
|
||||
|
||||
// Initialize the QueryClient
|
||||
const queryClient = new QueryClient();
|
||||
@@ -13,13 +14,16 @@ initializeAuthPersistence();
|
||||
|
||||
/**
|
||||
* Root Application Component.
|
||||
* Wraps the app with Redux Provider and React Query Provider.
|
||||
* Wraps the app with Redux Provider, React Query Provider, and AuthInitializer.
|
||||
* AuthInitializer ensures auth state is restored from persistence before routes are rendered.
|
||||
*/
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppRoutes />
|
||||
<AuthInitializer>
|
||||
<AppRoutes />
|
||||
</AuthInitializer>
|
||||
</QueryClientProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
44
apps/web/src/features/auth/AuthInitializer.tsx
Normal file
44
apps/web/src/features/auth/AuthInitializer.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { checkAuthStatus } from './authSlice';
|
||||
import type { RootState, AppDispatch } from '../../store/store';
|
||||
|
||||
interface AuthInitializerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthInitializer Component
|
||||
* Initializes authentication state from Firebase persistence on app load
|
||||
* Shows a loading screen until the initial auth check is complete
|
||||
* This prevents premature redirect to login before the persisted session is restored
|
||||
*/
|
||||
const AuthInitializer: React.FC<AuthInitializerProps> = ({ children }) => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const { isInitialized } = useSelector((state: RootState) => state.auth);
|
||||
|
||||
useEffect(() => {
|
||||
// Perform initial auth check when component mounts
|
||||
// This restores the persisted session from Firebase
|
||||
dispatch(checkAuthStatus());
|
||||
}, [dispatch]);
|
||||
|
||||
// Show loading state while initializing auth
|
||||
if (!isInitialized) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen bg-slate-50">
|
||||
<div className="text-center">
|
||||
<div className="inline-block">
|
||||
<div className="w-8 h-8 border-4 border-slate-200 border-t-primary rounded-full animate-spin"></div>
|
||||
</div>
|
||||
<p className="mt-4 text-slate-600">Loading application...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default AuthInitializer;
|
||||
@@ -17,6 +17,7 @@ interface AuthState {
|
||||
isAuthenticated: boolean;
|
||||
status: "idle" | "loading" | "succeeded" | "failed";
|
||||
error: string | null;
|
||||
isInitialized: boolean; // Track whether initial auth check has completed
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
@@ -24,6 +25,7 @@ const initialState: AuthState = {
|
||||
isAuthenticated: false,
|
||||
status: "idle",
|
||||
error: null,
|
||||
isInitialized: false, // Start as false until initial auth check completes
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -154,6 +156,9 @@ const authSlice = createSlice({
|
||||
|
||||
// Check auth status thunk
|
||||
builder
|
||||
.addCase(checkAuthStatus.pending, (state) => {
|
||||
state.status = "loading";
|
||||
})
|
||||
.addCase(checkAuthStatus.fulfilled, (state, action) => {
|
||||
if (action.payload) {
|
||||
state.user = action.payload;
|
||||
@@ -163,6 +168,11 @@ const authSlice = createSlice({
|
||||
state.isAuthenticated = false;
|
||||
}
|
||||
state.status = "idle";
|
||||
state.isInitialized = true; // Mark initialization as complete
|
||||
})
|
||||
.addCase(checkAuthStatus.rejected, (state) => {
|
||||
state.isInitialized = true; // Mark initialization as complete even on error
|
||||
state.isAuthenticated = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ export default function EditStaff() {
|
||||
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 () => {
|
||||
@@ -41,7 +42,8 @@ export default function EditStaff() {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await workforceService.entities.Staff.update(id, staffData);
|
||||
navigate("/staff");
|
||||
setStaff(staffData);
|
||||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to update staff", error);
|
||||
} finally {
|
||||
@@ -49,6 +51,10 @@ export default function EditStaff() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] gap-4">
|
||||
@@ -60,7 +66,7 @@ export default function EditStaff() {
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
title={`Edit: ${staff?.employee_name || 'Staff Member'}`}
|
||||
title={isEditing ? `Edit: ${staff?.employee_name || 'Staff Member'}` : staff?.employee_name || 'Staff Member'}
|
||||
subtitle={`Management of ${staff?.employee_name}'s professional records`}
|
||||
backAction={
|
||||
<Button
|
||||
@@ -74,11 +80,42 @@ export default function EditStaff() {
|
||||
}
|
||||
>
|
||||
{staff && (
|
||||
<StaffForm
|
||||
staff={staff}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
<div>
|
||||
{!isEditing && (
|
||||
<div className="mb-6 flex justify-end">
|
||||
<Button onClick={() => setIsEditing(true)} variant="secondary">
|
||||
Edit
|
||||
</Button>
|
||||
</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>
|
||||
)}
|
||||
<StaffForm
|
||||
staff={staff}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DashboardLayout>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user