feat: Add time block approval workflow and staff permission system
- Add TimeBlock approval status with manager approval workflow - Create core mixins for staff permission restrictions (DenyStaffWritePermission, etc.) - Add StaffDashboard page for staff-specific views - Refactor MyAvailability page for time block management - Update field mobile status machine and views - Add per-user permission overrides via JSONField - Document core mixins and permission system in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -158,8 +158,9 @@ export const useMyBlocks = () => {
|
||||
id: String(b.id),
|
||||
resource: b.resource ? String(b.resource) : null,
|
||||
})),
|
||||
resource_id: String(data.resource_id),
|
||||
resource_id: data.resource_id ? String(data.resource_id) : null,
|
||||
resource_name: data.resource_name,
|
||||
can_self_approve: data.can_self_approve,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -248,6 +249,75 @@ export const useToggleTimeBlock = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Time Block Approval Hooks
|
||||
// =============================================================================
|
||||
|
||||
export interface PendingReviewsResponse {
|
||||
count: number;
|
||||
pending_blocks: TimeBlockListItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch pending time block reviews (for managers/owners)
|
||||
*/
|
||||
export const usePendingReviews = () => {
|
||||
return useQuery<PendingReviewsResponse>({
|
||||
queryKey: ['time-block-pending-reviews'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/time-blocks/pending_reviews/');
|
||||
return {
|
||||
count: data.count,
|
||||
pending_blocks: data.pending_blocks.map((b: any) => ({
|
||||
...b,
|
||||
id: String(b.id),
|
||||
resource: b.resource ? String(b.resource) : null,
|
||||
})),
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to approve a time block
|
||||
*/
|
||||
export const useApproveTimeBlock = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, notes }: { id: string; notes?: string }) => {
|
||||
const { data } = await apiClient.post(`/time-blocks/${id}/approve/`, { notes });
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['time-blocks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['blocked-dates'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['my-blocks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['time-block-pending-reviews'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to deny a time block
|
||||
*/
|
||||
export const useDenyTimeBlock = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, notes }: { id: string; notes?: string }) => {
|
||||
const { data } = await apiClient.post(`/time-blocks/${id}/deny/`, { notes });
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['time-blocks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['blocked-dates'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['my-blocks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['time-block-pending-reviews'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to check for conflicts before creating a time block
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user