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>
7.6 KiB
7.6 KiB
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 assignmentOTHER: 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
# 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
- Create
ResourceTypemodel - 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)
- Migrate existing resources to use default types based on their
typefield - Eventually deprecate
Resource.typefield
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
// Add new tab to Settings.tsx
<Tab name="Resource Types">
<ResourceTypesManager />
</Tab>
ResourceTypesManager Component
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
// 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
// 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
- ✅ Create TypeScript types (DONE)
- ⏳ Create Django models (
ResourceType, addBusiness.logo) - ⏳ Create migrations with default data
- ⏳ Create serializers
- ⏳ Create API views and URLs
- ⏳ Add file upload handling for logos
Phase 2: Frontend Hooks & Types
- ✅ Create
useResourceTypeshook (DONE) - ⏳ Create logo upload utilities
- ⏳ Update
Resourcetype to includetypeId
Phase 3: UI Components
- ⏳ Add "Resource Types" tab to Settings
- ⏳ Create ResourceTypesManager component
- ⏳ Create ResourceTypeModal component
- ⏳ Add logo upload to Branding section
- ⏳ Update Resources page to use custom types
Phase 4: Migration & Testing
- ⏳ Test creating/editing/deleting types
- ⏳ Test logo upload/removal
- ⏳ Ensure backwards compatibility
- ⏳ Test resource creation with custom types
User Experience
Creating a Custom Resource Type
- Navigate to Business Settings → Resource Types
- Click "+ Add Resource Type"
- Enter name (e.g., "Massage Therapist")
- Select category: Staff or Other
- Click Save
- New type appears in list and is available when creating resources
Uploading a Logo
- Navigate to Business Settings → General → Branding
- Click "Upload Logo"
- Select image file (PNG, JPG, or SVG)
- Preview appears
- Click "Save Changes"
- Logo appears in header and customer-facing pages
Benefits
- Flexibility: Businesses can name types to match their industry
- Clarity: "Massage Therapist" is clearer than "Staff" for a spa
- Scalability: Add new types as business grows
- Branding: Logo upload improves professional appearance
- 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)