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:
@@ -2,11 +2,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Search, Filter, MoreHorizontal, Eye, ShieldCheck, Ban } from 'lucide-react';
|
||||
import { User } from '../../types';
|
||||
import { useBusinesses } from '../../hooks/usePlatform';
|
||||
|
||||
interface PlatformBusinessesProps {
|
||||
onMasquerade: (targetUser: User) => void;
|
||||
onMasquerade: (targetUser: { id: number; username?: string; name?: string; email?: string; role?: string }) => void;
|
||||
}
|
||||
|
||||
const PlatformBusinesses: React.FC<PlatformBusinessesProps> = ({ onMasquerade }) => {
|
||||
@@ -22,19 +21,14 @@ const PlatformBusinesses: React.FC<PlatformBusinessesProps> = ({ onMasquerade })
|
||||
const handleLoginAs = (business: any) => {
|
||||
// Use the owner data from the API response
|
||||
if (business.owner) {
|
||||
const targetOwner: User = {
|
||||
id: business.owner.id.toString(),
|
||||
// Pass owner info to masquerade - we only need the id
|
||||
onMasquerade({
|
||||
id: business.owner.id,
|
||||
username: business.owner.username,
|
||||
name: business.owner.name,
|
||||
name: business.owner.full_name,
|
||||
email: business.owner.email,
|
||||
role: business.owner.role,
|
||||
business_id: business.id.toString(),
|
||||
business_subdomain: business.subdomain,
|
||||
is_active: true,
|
||||
is_staff: false,
|
||||
is_superuser: false,
|
||||
};
|
||||
onMasquerade(targetOwner);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,14 +124,14 @@ const PlatformBusinesses: React.FC<PlatformBusinessesProps> = ({ onMasquerade })
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-gray-500 dark:text-gray-400">
|
||||
{new Date(biz.created_at).toLocaleDateString()}
|
||||
{new Date(biz.created_on).toLocaleDateString()}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right">
|
||||
<button
|
||||
onClick={() => handleLoginAs(biz)}
|
||||
className="text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300 font-medium text-xs inline-flex items-center gap-1 px-3 py-1 border border-indigo-200 dark:border-indigo-800 rounded-lg hover:bg-indigo-50 dark:hover:bg-indigo-900/30 transition-colors mr-2"
|
||||
disabled={!biz.owner}
|
||||
title={!biz.owner ? 'No owner assigned' : `Masquerade as ${biz.owner.name}`}
|
||||
title={!biz.owner ? 'No owner assigned' : `Masquerade as ${biz.owner.full_name}`}
|
||||
>
|
||||
<Eye size={14} /> {t('platform.masquerade')}
|
||||
</button>
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Search, Filter, Eye, Shield, User as UserIcon } from 'lucide-react';
|
||||
import { User } from '../../types';
|
||||
import { usePlatformUsers } from '../../hooks/usePlatform';
|
||||
|
||||
interface PlatformUsersProps {
|
||||
onMasquerade: (targetUser: User) => void;
|
||||
onMasquerade: (targetUser: { id: number; username?: string; name?: string; email?: string; role?: string }) => void;
|
||||
}
|
||||
|
||||
const PlatformUsers: React.FC<PlatformUsersProps> = ({ onMasquerade }) => {
|
||||
@@ -36,20 +35,14 @@ const PlatformUsers: React.FC<PlatformUsersProps> = ({ onMasquerade }) => {
|
||||
};
|
||||
|
||||
const handleMasquerade = (platformUser: any) => {
|
||||
// Convert platform user to User type for masquerade
|
||||
const targetUser: User = {
|
||||
id: platformUser.id.toString(),
|
||||
// Pass user info to masquerade - we only need the id
|
||||
onMasquerade({
|
||||
id: platformUser.id,
|
||||
username: platformUser.username,
|
||||
name: platformUser.name || platformUser.username,
|
||||
name: platformUser.full_name || platformUser.username,
|
||||
email: platformUser.email,
|
||||
role: platformUser.role || 'customer',
|
||||
business_id: platformUser.business?.toString() || null,
|
||||
business_subdomain: platformUser.business_subdomain || null,
|
||||
is_active: platformUser.is_active,
|
||||
is_staff: platformUser.is_staff,
|
||||
is_superuser: platformUser.is_superuser,
|
||||
};
|
||||
onMasquerade(targetUser);
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
Reference in New Issue
Block a user