Industry Templates
This guide covers how to view, manage, and understand industry templates that enable Shyfts's multi-industry support.
Overview
What Are Industry Templates?
Industry templates are pre-configured setups that customise Shyfts for different business types:
| Template | Display Name | Target Industry |
|---|---|---|
| gp_practice | GP Practice | Medical practices |
| dental_practice | Dental Practice | Dental clinics |
| restaurant | Restaurant | Food service |
| office | Office | General business |
Template Components
Each template defines:
| Component | Purpose |
|---|---|
| Staff Roles | Role definitions with hierarchy levels |
| Room Types | Workspace/area configurations |
| Terminology | Industry-specific language |
| Permissions | Role-based access rights |
| Default Settings | Pre-configured options |
Default Templates
GP Practice Template
// Source: src/app/api/seed-templates/route.ts:9-93
{
name: 'gp_practice',
display_name: 'GP Practice',
staff_roles: {
gp: {
color: '#4299E1',
permissions: ['MANAGE_PATIENTS', 'PRESCRIBE', 'VIEW_MEDICAL_RECORDS'],
name: 'GP',
hierarchy_level: 1
},
nurse: {
color: '#48BB78',
permissions: ['ASSIST_PATIENTS', 'BASIC_CARE', 'VIEW_SCHEDULES'],
name: 'Nurse',
hierarchy_level: 2
},
hca: {
color: '#9f7aea',
permissions: ['HEALTHCARE_ASSISTANCE', 'PATIENT_SUPPORT'],
name: 'HCA',
hierarchy_level: 3
},
receptionist: {
color: '#ED8936',
permissions: ['RECEPTION', 'ADMINISTRATION', 'MANAGE_APPOINTMENTS'],
name: 'Receptionist',
hierarchy_level: 3
},
clinic_administrator: {
color: '#FF5733',
permissions: ['MANAGE_STAFF', 'HR', 'OPERATIONS', 'FINANCIAL_OVERSIGHT'],
name: 'Practice Administrator',
hierarchy_level: 1
}
},
terminology: {
staff: 'Staff',
company: 'Practice',
manager: 'Practice Administrator',
shifts: 'Surgery Sessions',
rooms: 'Consultation Rooms'
}
}
GP Practice Roles
| Role | Colour | Hierarchy | Permissions |
|---|---|---|---|
| GP | Blue (#4299E1) | 1 (Top) | MANAGE_PATIENTS, PRESCRIBE, VIEW_MEDICAL_RECORDS |
| Practice Administrator | Red (#FF5733) | 1 (Top) | MANAGE_STAFF, HR, OPERATIONS, FINANCIAL_OVERSIGHT |
| Nurse | Green (#48BB78) | 2 | ASSIST_PATIENTS, BASIC_CARE, VIEW_SCHEDULES |
| HCA | Purple (#9f7aea) | 3 | HEALTHCARE_ASSISTANCE, PATIENT_SUPPORT |
| Receptionist | Orange (#ED8936) | 3 | RECEPTION, ADMINISTRATION, MANAGE_APPOINTMENTS |
| Admin | Indigo (#667eea) | 3 | ADMINISTRATION, DATA_MANAGEMENT |
GP Practice Room Types
| Room | Capacity | Equipment |
|---|---|---|
| GP Room | 2 | Examination table, computer, medical equipment |
| HCA Room | 2 | Treatment table, medical supplies |
| Reception | 3 | Reception desk, phone system, computer |
| Non Clinical Room | 4 | Desk, computer, meeting table |
| WFH | 1 | Remote access |
Dental Practice Template
// Source: src/app/api/seed-templates/route.ts:95-159
{
name: 'dental_practice',
display_name: 'Dental Practice',
staff_roles: {
dentist: {
color: '#0EA5E9',
permissions: ['PERFORM_PROCEDURES', 'DIAGNOSE', 'PRESCRIBE'],
name: 'Dentist',
hierarchy_level: 1
},
dental_hygienist: {
color: '#10B981',
permissions: ['CLEANINGS', 'PATIENT_EDUCATION', 'BASIC_PROCEDURES'],
name: 'Dental Hygienist',
hierarchy_level: 2
},
dental_assistant: {
color: '#F59E0B',
permissions: ['ASSIST_PROCEDURES', 'PATIENT_PREP', 'EQUIPMENT_SETUP'],
name: 'Dental Assistant',
hierarchy_level: 3
},
clinic_manager: {
color: '#8B5CF6',
permissions: ['MANAGE_STAFF', 'OPERATIONS', 'FINANCIAL_MANAGEMENT'],
name: 'Practice Manager',
hierarchy_level: 1
}
},
terminology: {
staff: 'Dental Team',
company: 'Practice',
manager: 'Practice Manager',
shifts: 'Appointment Blocks',
rooms: 'Treatment Rooms'
}
}
Dental Practice Roles
| Role | Colour | Hierarchy | Permissions |
|---|---|---|---|
| Dentist | Sky Blue (#0EA5E9) | 1 (Top) | PERFORM_PROCEDURES, DIAGNOSE, PRESCRIBE |
| Practice Manager | Violet (#8B5CF6) | 1 (Top) | MANAGE_STAFF, OPERATIONS, FINANCIAL_MANAGEMENT |
| Dental Hygienist | Emerald (#10B981) | 2 | CLEANINGS, PATIENT_EDUCATION, BASIC_PROCEDURES |
| Dental Assistant | Amber (#F59E0B) | 3 | ASSIST_PROCEDURES, PATIENT_PREP, EQUIPMENT_SETUP |
| Receptionist | Pink (#EC4899) | 3 | SCHEDULE_APPOINTMENTS, BILLING, PATIENT_RECORDS |
Dental Practice Room Types
| Room | Capacity | Equipment |
|---|---|---|
| Treatment Room | 2 | Dental chair, X-ray, dental tools, suction |
| Hygiene Room | 2 | Dental chair, cleaning tools, polishing equipment |
| Consultation Room | 3 | Desk, computer, patient education materials |
| Waiting Area | 15 | Seating, reception desk, magazines |
Restaurant Template
// Source: src/app/api/seed-templates/route.ts:160-219
{
name: 'restaurant',
display_name: 'Restaurant',
staff_roles: {
head_chef: {
color: '#DC2626',
permissions: ['MANAGE_KITCHEN', 'MENU_PLANNING', 'SUPERVISE_COOKS'],
name: 'Head Chef',
hierarchy_level: 1
},
sous_chef: {
color: '#EA580C',
permissions: ['ASSIST_HEAD_CHEF', 'FOOD_PREP', 'QUALITY_CONTROL'],
name: 'Sous Chef',
hierarchy_level: 2
},
line_cook: {
color: '#F59E0B',
permissions: ['FOOD_PREPARATION', 'STATION_MANAGEMENT'],
name: 'Line Cook',
hierarchy_level: 3
},
server: {
color: '#10B981',
permissions: ['TAKE_ORDERS', 'SERVE_CUSTOMERS', 'HANDLE_PAYMENTS'],
name: 'Server',
hierarchy_level: 3
},
manager: {
color: '#8B5CF6',
permissions: ['MANAGE_STAFF', 'INVENTORY', 'CUSTOMER_SERVICE'],
name: 'Restaurant Manager',
hierarchy_level: 1
}
},
terminology: {
staff: 'Team Members',
company: 'Restaurant',
manager: 'Restaurant Manager',
shifts: 'Service Shifts',
rooms: 'Stations'
}
}
Restaurant Roles
| Role | Colour | Hierarchy | Permissions |
|---|---|---|---|
| Head Chef | Red (#DC2626) | 1 (Top) | MANAGE_KITCHEN, MENU_PLANNING, SUPERVISE_COOKS |
| Restaurant Manager | Violet (#8B5CF6) | 1 (Top) | MANAGE_STAFF, INVENTORY, CUSTOMER_SERVICE |
| Sous Chef | Orange (#EA580C) | 2 | ASSIST_HEAD_CHEF, FOOD_PREP, QUALITY_CONTROL |
| Line Cook | Amber (#F59E0B) | 3 | FOOD_PREPARATION, STATION_MANAGEMENT |
| Server | Emerald (#10B981) | 3 | TAKE_ORDERS, SERVE_CUSTOMERS, HANDLE_PAYMENTS |
| Host/Hostess | Blue (#3B82F6) | 3 | GREET_CUSTOMERS, MANAGE_SEATING, RESERVATIONS |
Restaurant Room Types
| Room | Capacity | Equipment |
|---|---|---|
| Kitchen | 8 | Stoves, ovens, prep stations, dishwasher |
| Dining Room | 50 | Tables, chairs, POS system |
| Bar Area | 15 | Bar stools, drink station, cash register |
| Prep Area | 4 | Cutting boards, storage, refrigeration |
Office Template
// Source: src/app/api/seed-templates/route.ts:220-273
{
name: 'office',
display_name: 'Office',
staff_roles: {
manager: {
color: '#7C3AED',
permissions: ['MANAGE_TEAM', 'APPROVE_REQUESTS', 'STRATEGIC_PLANNING'],
name: 'Manager',
hierarchy_level: 1
},
supervisor: {
color: '#2563EB',
permissions: ['SUPERVISE_STAFF', 'DAILY_OPERATIONS', 'REPORTING'],
name: 'Supervisor',
hierarchy_level: 2
},
employee: {
color: '#059669',
permissions: ['COMPLETE_TASKS', 'ATTEND_MEETINGS', 'BASIC_ADMIN'],
name: 'Employee',
hierarchy_level: 3
},
admin_assistant: {
color: '#EA580C',
permissions: ['ADMIN_SUPPORT', 'SCHEDULING', 'CORRESPONDENCE'],
name: 'Administrative Assistant',
hierarchy_level: 3
}
},
terminology: {
staff: 'Employees',
company: 'Office',
manager: 'Manager',
shifts: 'Work Shifts',
rooms: 'Workspaces'
}
}
Office Roles
| Role | Colour | Hierarchy | Permissions |
|---|---|---|---|
| Manager | Purple (#7C3AED) | 1 (Top) | MANAGE_TEAM, APPROVE_REQUESTS, STRATEGIC_PLANNING |
| Supervisor | Blue (#2563EB) | 2 | SUPERVISE_STAFF, DAILY_OPERATIONS, REPORTING |
| Employee | Emerald (#059669) | 3 | COMPLETE_TASKS, ATTEND_MEETINGS, BASIC_ADMIN |
| Admin Assistant | Orange (#EA580C) | 3 | ADMIN_SUPPORT, SCHEDULING, CORRESPONDENCE |
| Intern | Red (#DC2626) | 4 | LEARNING_TASKS, ASSIST_PROJECTS, BASIC_DUTIES |
Office Room Types
| Room | Capacity | Equipment |
|---|---|---|
| Office Space | 10 | Desks, computers, phones, filing cabinets |
| Meeting Room | 12 | Conference table, projector, whiteboard, video conference |
| Reception Area | 5 | Reception desk, seating, phone system |
| Break Room | 20 | Tables, chairs, kitchen facilities, vending machines |
Template Data Structure
Database Schema
// Source: src/types/database.types.ts:761-795
industry_templates: {
Row: {
id: string
name: string
display_name: string
description: string | null
staff_roles: Json
room_types: Json
terminology: Json
email_templates: Json | null
default_settings: Json | null
is_active: boolean | null
created_at: string | null
updated_at: string | null
}
}
TypeScript Interfaces
// Source: src/types/industry-templates.ts:8-54
interface StaffRoleTemplate {
display_name: string
color: string
permissions: string[]
hierarchy_level: number
can_manage_staff?: boolean
can_manage_schedules?: boolean
can_view_reports?: boolean
}
interface RoomTypeTemplate {
display_name: string
capacity: number
equipment: string[]
}
interface IndustryTerminology {
staff: string
staffPlural: string
customer: string
customerPlural: string
appointment: string
appointmentPlural: string
[key: string]: string
}
interface IndustryTemplate {
id: string
name: string
display_name: string
description?: string | null
staff_roles: Record<string, StaffRoleTemplate>
room_types: Record<string, RoomTypeTemplate>
terminology: IndustryTerminology
email_templates?: Record<string, EmailTemplate> | null
default_settings?: Record<string, unknown> | null
is_active?: boolean | null
created_at?: string | null
updated_at?: string | null
}
Template API
Get Template by ID
Endpoint: GET /api/industry-templates/[id]
// Source: src/app/api/industry-templates/[id]/route.ts:8-39
export async function GET(
_request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const supabase = await createClient()
// Check authentication
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return ApiResponses.unauthorized()
}
const { data: template, error } = await supabase
.from('industry_templates')
.select('*')
.eq('id', params.id)
.single()
if (error) {
throw error
}
if (!template) {
return ApiResponses.notFound('Industry template not found')
}
return ApiResponses.success({ data: template })
} catch (error) {
return handleApiError(error)
}
}
Get All Templates
// Source: src/utils/api/industryTemplates.ts:36-54
export async function getIndustryTemplates(): Promise<IndustryTemplate[]> {
const { data, error } = await supabase
.from('industry_templates')
.select('*')
.order('name')
if (error) {
throw new Error(`Failed to fetch industry templates: ${error.message}`)
}
const templates: IndustryTemplate[] = []
for (const dbTemplate of data || []) {
const converted = convertDatabaseToIndustryTemplate(dbTemplate)
if (converted) {
templates.push(converted)
}
}
return templates
}
Response Format
// Template details response
{
success: true,
data: {
data: {
id: "template-uuid",
name: "gp_practice",
display_name: "GP Practice",
staff_roles: { /* role definitions */ },
room_types: { /* room definitions */ },
terminology: { /* industry terms */ },
is_active: true,
created_at: "2025-01-01T00:00:00Z"
}
}
}
Seed Templates API
Seed Default Templates
Endpoint: POST /api/seed-templates
// Source: src/app/api/seed-templates/route.ts:276-354
export async function POST() {
try {
const supabase = await createClient()
const adminClient = await createAdminClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (getAuthRole(user) !== 'SYSTEM_ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
if (process.env.NODE_ENV === 'production') {
return NextResponse.json(
{ error: 'Template seeding disabled in production' },
{ status: 403 }
)
}
// Check if templates already exist
const { data: existing } = await supabase
.from('industry_templates')
.select('id, name, display_name')
// Insert templates using admin client
const { data, error } = await adminClient
.from('industry_templates')
.insert(templates)
.select()
return NextResponse.json({
success: true,
message: `Successfully seeded ${data.length} industry templates`,
templates: data.map(t => ({ id: t.id, name: t.name }))
})
} catch (error) {
return handleApiError(error)
}
}
Seed Response
// Successful seed response
{
success: true,
message: "Successfully seeded 4 industry templates",
templates: [
{ id: "uuid-1", name: "gp_practice" },
{ id: "uuid-2", name: "dental_practice" },
{ id: "uuid-3", name: "restaurant" },
{ id: "uuid-4", name: "office" }
]
}
// Templates already exist response
{
success: true,
message: "All templates already exist",
existing: [
{ name: "gp_practice", display_name: "GP Practice" },
{ name: "dental_practice", display_name: "Dental Practice" },
{ name: "restaurant", display_name: "Restaurant" },
{ name: "office", display_name: "Office" }
]
}
Template Client Utilities
Fetch Template by Name
// Source: src/utils/api/industryTemplates.ts:79-94
export async function getIndustryTemplateByName(
name: string
): Promise<IndustryTemplate | null> {
const { data, error } = await supabase
.from('industry_templates')
.select('*')
.eq('name', name)
.single()
if (error) {
if (error.code === 'PGRST116') {
return null // Template not found
}
throw new Error(`Failed to fetch industry template: ${error.message}`)
}
return convertDatabaseToIndustryTemplate(data)
}
Create Template (System Admin)
// Source: src/utils/api/industryTemplates.ts:99-127
export async function createIndustryTemplate(
data: CreateIndustryTemplateData
): Promise<IndustryTemplate> {
const dbData: TablesInsert<'industry_templates'> = {
name: data.name,
display_name: data.name,
staff_roles: data.staff_roles as unknown as Json,
room_types: data.room_types as unknown as Json,
terminology: data.terminology as unknown as Json,
email_templates: (data.email_templates || []) as unknown as Json,
default_settings: (data.default_settings || {}) as unknown as Json,
is_active: true
}
const { data: template, error } = await supabase
.from('industry_templates')
.insert([dbData])
.select()
.single()
if (error) {
throw new Error(`Failed to create industry template: ${error.message}`)
}
return convertDatabaseToIndustryTemplate(template)
}
Update Template
// Source: src/utils/api/industryTemplates.ts:132-172
export async function updateIndustryTemplate(
id: string,
data: Partial<CreateIndustryTemplateData>
): Promise<IndustryTemplate> {
const updateData: Partial<TablesInsert<'industry_templates'>> = {}
if (data.name !== undefined) updateData.name = data.name
if (data.staff_roles !== undefined) {
updateData.staff_roles = data.staff_roles as unknown as Json
}
if (data.room_types !== undefined) {
updateData.room_types = data.room_types as unknown as Json
}
if (data.terminology !== undefined) {
updateData.terminology = data.terminology as unknown as Json
}
const { data: template, error } = await supabase
.from('industry_templates')
.update(updateData)
.eq('id', id)
.select()
.single()
if (error) {
throw new Error(`Failed to update industry template: ${error.message}`)
}
return convertDatabaseToIndustryTemplate(template)
}
Delete Template
// Source: src/utils/api/industryTemplates.ts:177-186
export async function deleteIndustryTemplate(id: string): Promise<void> {
const { error } = await supabase
.from('industry_templates')
.delete()
.eq('id', id)
if (error) {
throw new Error(`Failed to delete industry template: ${error.message}`)
}
}
Terminology System
How Terminology Works
Industry templates provide context-aware language throughout the platform:
| Generic Term | GP Practice | Dental Practice | Restaurant | Office |
|---|---|---|---|---|
| staff | Staff | Dental Team | Team Members | Employees |
| company | Practice | Practice | Restaurant | Office |
| manager | Practice Administrator | Practice Manager | Restaurant Manager | Manager |
| shifts | Surgery Sessions | Appointment Blocks | Service Shifts | Work Shifts |
| rooms | Consultation Rooms | Treatment Rooms | Stations | Workspaces |
Using Terminology in Components
// Get terminology from industry context
const { terminology } = useIndustry()
// Use dynamic terms
<h1>Manage Your {terminology.staff}</h1>
<p>Schedule {terminology.shifts} for your {terminology.company}</p>
Terminology Interface
// Source: src/types/industry-templates.ts:25-33
interface IndustryTerminology {
staff: string
staffPlural: string
customer: string
customerPlural: string
appointment: string
appointmentPlural: string
[key: string]: string
}
Hierarchy Levels
Understanding Hierarchy
Each staff role has a hierarchy level that determines authority:
| Level | Description | Example Roles |
|---|---|---|
| 1 | Management/Senior | GP, Practice Manager, Head Chef |
| 2 | Mid-level/Supervisor | Nurse, Sous Chef, Supervisor |
| 3 | Standard Staff | Receptionist, Server, Employee |
| 4 | Entry Level/Support | Temp, Intern, Cleaner |
Hierarchy Usage
// Roles with hierarchy_level = 1 are Company Managers
const { count: staffManagerCount } = await supabase
.from('staff')
.select('*, staff_roles!inner(*)', { count: 'exact', head: true })
.eq('staff_roles.hierarchy_level', 1)
.eq('is_active', true)
Template Application
When Templates Are Applied
Templates are applied during company creation:
1. Company Creation Wizard started
2. Industry template selected (e.g., GP Practice)
3. Template roles → Staff Roles table
4. Template room types → Room Types table
5. Template terminology → Company settings
6. Company uses industry-specific UI
Company-Template Relationship
// Companies reference industry templates
interface Company {
id: string
name: string
industry_template_id: string // Reference to template
// ... other fields
}
Template Changes
Once a company is created with a template, changing the template is not supported. Template changes would affect roles, rooms, and terminology in unpredictable ways.
Access Control
Who Can Manage Templates
| Role | View Templates | Create | Update | Delete |
|---|---|---|---|---|
| System Admin | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Company Manager | ✅ Yes (read-only) | ❌ No | ❌ No | ❌ No |
| Staff | ❌ No | ❌ No | ❌ No | ❌ No |
Seeding Restrictions
// Seeding disabled in production
if (process.env.NODE_ENV === 'production') {
return NextResponse.json(
{ error: 'Template seeding disabled in production' },
{ status: 403 }
)
}
Best Practices
Template Management
| Practice | Description |
|---|---|
| Don't modify in production | Create new templates instead of editing existing |
| Test thoroughly | Test new templates in development first |
| Document changes | Keep records of template modifications |
| Backup before changes | Export template data before updates |
Creating Custom Templates
| Consideration | Guidance |
|---|---|
| Role names | Use clear, industry-appropriate names |
| Hierarchy | Ensure logical hierarchy (1 = top) |
| Colours | Use distinct, accessible colours |
| Terminology | Match industry conventions |
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Template not found | Invalid ID | Verify template exists |
| Seed failed | Templates exist | Check existing templates first |
| Forbidden error | Not System Admin | Login as System Admin |
| Production blocked | Environment check | Use development environment |
Error Messages
| Error | Meaning | Action |
|---|---|---|
| "Unauthorized" | Not logged in | Login first |
| "Forbidden" | Not System Admin | Use admin account |
| "Template seeding disabled" | Production environment | Use development |
| "Template not found" | Invalid template ID | Verify ID exists |
Related Documentation
- Creating Companies - Company creation with templates
- Platform Settings - System configuration
- User Management - User administration
- Staff Roles - Role configuration
Source Files:
src/app/api/industry-templates/[id]/route.ts- Template API endpointsrc/app/api/seed-templates/route.ts- Template seedingsrc/utils/api/industryTemplates.ts- Client utilitiessrc/types/industry-templates.ts- Type definitionssrc/types/database.types.ts- Database schema