feat: Add photo galleries to services, resource types management, and UI improvements
Major features: - Add drag-and-drop photo gallery to Service create/edit modals - Add Resource Types management section to Settings (CRUD for custom types) - Add edit icon consistency to Resources table (pencil icon in actions) - Improve Services page with drag-to-reorder and customer preview mockup Backend changes: - Add photos JSONField to Service model with migration - Add ResourceType model with category (STAFF/OTHER), description fields - Add ResourceTypeViewSet with CRUD operations - Add service reorder endpoint for display order Frontend changes: - Services page: two-column layout, drag-reorder, photo upload - Settings page: Resource Types tab with full CRUD modal - Resources page: Edit icon in actions column instead of row click - Sidebar: Payments link visibility based on role and paymentsEnabled - Update types.ts with Service.photos and ResourceTypeDefinition Note: Removed photos from ResourceType (kept only for Service) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ export const useServices = () => {
|
||||
durationMinutes: s.duration || s.duration_minutes,
|
||||
price: parseFloat(s.price),
|
||||
description: s.description || '',
|
||||
displayOrder: s.display_order ?? 0,
|
||||
photos: s.photos || [],
|
||||
}));
|
||||
},
|
||||
retry: false, // Don't retry on 404 - endpoint may not exist yet
|
||||
@@ -43,6 +45,8 @@ export const useService = (id: string) => {
|
||||
durationMinutes: data.duration || data.duration_minutes,
|
||||
price: parseFloat(data.price),
|
||||
description: data.description || '',
|
||||
displayOrder: data.display_order ?? 0,
|
||||
photos: data.photos || [],
|
||||
};
|
||||
},
|
||||
enabled: !!id,
|
||||
@@ -63,6 +67,7 @@ export const useCreateService = () => {
|
||||
duration: serviceData.durationMinutes,
|
||||
price: serviceData.price.toString(),
|
||||
description: serviceData.description,
|
||||
photos: serviceData.photos || [],
|
||||
};
|
||||
|
||||
const { data } = await apiClient.post('/api/services/', backendData);
|
||||
@@ -87,6 +92,7 @@ export const useUpdateService = () => {
|
||||
if (updates.durationMinutes) backendData.duration = updates.durationMinutes;
|
||||
if (updates.price) backendData.price = updates.price.toString();
|
||||
if (updates.description !== undefined) backendData.description = updates.description;
|
||||
if (updates.photos !== undefined) backendData.photos = updates.photos;
|
||||
|
||||
const { data } = await apiClient.patch(`/api/services/${id}/`, backendData);
|
||||
return data;
|
||||
@@ -112,3 +118,22 @@ export const useDeleteService = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to reorder services (drag and drop)
|
||||
*/
|
||||
export const useReorderServices = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (orderedIds: string[]) => {
|
||||
// Convert string IDs to numbers for the backend
|
||||
const order = orderedIds.map(id => parseInt(id, 10));
|
||||
const { data } = await apiClient.post('/api/services/reorder/', { order });
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['services'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user