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:
poduck
2025-11-28 01:11:53 -05:00
parent a7c756a8ec
commit b10426fbdb
52 changed files with 4259 additions and 356 deletions

281
RESOURCE_TYPES_PLAN.md Normal file
View File

@@ -0,0 +1,281 @@
# Custom Resource Types & Logo Upload - Implementation Plan
## Overview
Allow businesses to create custom resource types (e.g., "Stylist", "Massage Therapist", "Treatment Room") instead of hardcoded types. Also add logo upload to business branding.
## Features
### 1. Custom Resource Types
- **Default Types**: Always include one "Staff" type (cannot be deleted)
- **Custom Names**: Users can name types whatever they want (e.g., "Hair Stylist", "Nail Technician", "Massage Room")
- **Categories**: Each type has a category:
- `STAFF`: Requires staff member assignment
- `OTHER`: No staff assignment needed
- **Management**: Add, edit, delete custom types in Business Settings
### 2. Logo Upload
- Upload business logo in branding section
- Support PNG, JPG, SVG
- Preview before saving
- Used in customer-facing pages
## Database Changes
### Backend Models
```python
# smoothschedule/schedule/models.py
class ResourceType(models.Model):
"""Custom resource type definitions per business"""
business = models.ForeignKey('tenants.Business', on_delete=models.CASCADE, related_name='resource_types')
name = models.CharField(max_length=100) # "Stylist", "Treatment Room", etc.
category = models.CharField(max_length=10, choices=[
('STAFF', 'Staff'),
('OTHER', 'Other'),
])
is_default = models.BooleanField(default=False) # Cannot be deleted
icon_name = models.CharField(max_length=50, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['business', 'name']
ordering = ['name']
def __str__(self):
return f"{self.business.name} - {self.name}"
class Resource(models.Model):
"""Update existing Resource model"""
# ... existing fields ...
# NEW FIELD - references custom resource type
resource_type = models.ForeignKey(
ResourceType,
on_delete=models.PROTECT, # Cannot delete type if resources use it
related_name='resources',
null=True, # For migration
blank=True
)
# DEPRECATED - keep for backwards compatibility during migration
type = models.CharField(
max_length=20,
choices=[('STAFF', 'Staff'), ('ROOM', 'Room'), ('EQUIPMENT', 'Equipment')],
default='STAFF'
)
# smoothschedule/tenants/models.py
class Business(models.Model):
# ... existing fields ...
# NEW FIELD - logo upload
logo = models.ImageField(upload_to='business_logos/', null=True, blank=True)
```
### Migration Strategy
1. Create `ResourceType` model
2. For each business, create default resource types:
- "Staff" (category=STAFF, is_default=True)
- "Room" (category=OTHER, is_default=True)
- "Equipment" (category=OTHER, is_default=True)
3. Migrate existing resources to use default types based on their `type` field
4. Eventually deprecate `Resource.type` field
## API Endpoints
### Resource Types
```
GET /api/resource-types/ # List business resource types
POST /api/resource-types/ # Create new type
PATCH /api/resource-types/{id}/ # Update type
DELETE /api/resource-types/{id}/ # Delete type (if not in use & not default)
```
### Logo Upload
```
POST /api/business/logo/ # Upload logo
DELETE /api/business/logo/ # Remove logo
```
## Frontend Implementation
### 1. Business Settings - Resource Types Tab
```tsx
// Add new tab to Settings.tsx
<Tab name="Resource Types">
<ResourceTypesManager />
</Tab>
```
### ResourceTypesManager Component
```tsx
interface ResourceTypesManagerProps {}
const ResourceTypesManager: React.FC = () => {
const { data: types = [] } = useResourceTypes();
const createMutation = useCreateResourceType();
const updateMutation = useUpdateResourceType();
const deleteMutation = useDeleteResourceType();
return (
<div>
<h3>Resource Types</h3>
<p>Customize how you categorize your bookable resources.</p>
{/* List of types */}
{types.map(type => (
<TypeCard
key={type.id}
type={type}
onEdit={handleEdit}
onDelete={handleDelete}
canDelete={!type.isDefault && !type.inUse}
/>
))}
{/* Add new type button */}
<button onClick={openCreateModal}>
+ Add Resource Type
</button>
{/* Create/Edit Modal */}
<ResourceTypeModal
type={editingType}
onSave={handleSave}
onClose={closeModal}
/>
</div>
);
};
```
### 2. Logo Upload in Branding Section
```tsx
// In Settings.tsx, Branding section
<div className="space-y-4">
<h4>Business Logo</h4>
{/* Logo preview */}
{business.logoUrl && (
<div className="relative w-32 h-32">
<img src={business.logoUrl} alt="Business logo" />
<button onClick={removeLogo}>
<X size={16} />
</button>
</div>
)}
{/* Upload button */}
<input
type="file"
accept="image/png,image/jpeg,image/svg+xml"
onChange={handleLogoUpload}
className="hidden"
id="logo-upload"
/>
<label htmlFor="logo-upload">
<Upload size={16} />
Upload Logo
</label>
</div>
```
### 3. Update Resources.tsx to use custom types
```tsx
// Resources.tsx
const Resources: React.FC = () => {
const { data: resourceTypes = [] } = useResourceTypes();
return (
<ResourceModal>
<select name="resourceType">
{resourceTypes.map(type => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
{/* Show staff selector if selected type is STAFF category */}
{selectedType?.category === 'STAFF' && (
<StaffSelector required />
)}
</ResourceModal>
);
};
```
## Implementation Steps
### Phase 1: Backend Foundation
1. ✅ Create TypeScript types (DONE)
2. ⏳ Create Django models (`ResourceType`, add `Business.logo`)
3. ⏳ Create migrations with default data
4. ⏳ Create serializers
5. ⏳ Create API views and URLs
6. ⏳ Add file upload handling for logos
### Phase 2: Frontend Hooks & Types
1. ✅ Create `useResourceTypes` hook (DONE)
2. ⏳ Create logo upload utilities
3. ⏳ Update `Resource` type to include `typeId`
### Phase 3: UI Components
1. ⏳ Add "Resource Types" tab to Settings
2. ⏳ Create ResourceTypesManager component
3. ⏳ Create ResourceTypeModal component
4. ⏳ Add logo upload to Branding section
5. ⏳ Update Resources page to use custom types
### Phase 4: Migration & Testing
1. ⏳ Test creating/editing/deleting types
2. ⏳ Test logo upload/removal
3. ⏳ Ensure backwards compatibility
4. ⏳ Test resource creation with custom types
## User Experience
### Creating a Custom Resource Type
1. Navigate to Business Settings → Resource Types
2. Click "+ Add Resource Type"
3. Enter name (e.g., "Massage Therapist")
4. Select category: Staff or Other
5. Click Save
6. New type appears in list and is available when creating resources
### Uploading a Logo
1. Navigate to Business Settings → General → Branding
2. Click "Upload Logo"
3. Select image file (PNG, JPG, or SVG)
4. Preview appears
5. Click "Save Changes"
6. Logo appears in header and customer-facing pages
## Benefits
1. **Flexibility**: Businesses can name types to match their industry
2. **Clarity**: "Massage Therapist" is clearer than "Staff" for a spa
3. **Scalability**: Add new types as business grows
4. **Branding**: Logo upload improves professional appearance
5. **User-Friendly**: Simple UI for non-technical users
## Notes
- Default "Staff" type cannot be deleted (ensures at least one staff type exists)
- Cannot delete types that are in use by existing resources
- Logo file size limit: 5MB
- Logo recommended dimensions: 500x500px (square) or 500x200px (wide)