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:
281
RESOURCE_TYPES_PLAN.md
Normal file
281
RESOURCE_TYPES_PLAN.md
Normal 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)
|
||||
Reference in New Issue
Block a user