Skip to main content

Opening Hours

This guide covers configuring and managing your company's business hours, including daily schedules, closed days, and timezone handling.


Overview

What are Opening Hours?

Opening hours define when your business operates:

  1. Daily Schedule - Open and close times for each day of the week
  2. Closed Days - Days when the business is closed
  3. Scheduling Integration - Affects shift scheduling and availability

Access Levels

RoleViewEditRequest Changes
System AdminN/A
Company Manager
Staff
note

Only System Administrators can directly modify opening hours. Company Managers can request changes which notifies all System Admins for review.


Requesting Changes

For Company Managers

If you need to modify opening hours, you can submit a change request:

  1. Navigate to DashboardCompany SettingsHours tab
  2. Click the "Request Changes" button
  3. Enter a detailed description of the changes needed
  4. Submit the request

What Happens Next

  1. All System Administrators receive a notification
  2. A System Admin will review your request
  3. They may contact you for clarification
  4. Changes will be applied if approved

Tips for Good Requests

  • Be specific: "Change Friday closing time from 17:30 to 19:00"
  • Include context: "To accommodate evening appointments"
  • Mention dates: "Starting from 1st February"
tip

The more detail you provide, the faster your request can be processed.


Viewing Opening Hours

  1. Navigate to SettingsOpening Hours from the sidebar
  2. View the weekly schedule with times for each day
  3. Closed days are clearly marked

Opening Hours API

Endpoint: GET /api/companies/[companyId]/hours

// Source: src/app/api/companies/[companyId]/hours/route.ts:18-60
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ companyId: string }> }
) {
const supabase = await createClient()
const resolvedParams = await params

// Fetch opening hours ordered by day of week
const { data: openingHours, error } = await supabase
.from('company_opening_hours')
.select('*')
.eq('company_id', resolvedParams.companyId)
.order('day_of_week', { ascending: true })

if (error) {
return ApiResponses.error(
new DatabaseError('Failed to fetch opening hours', 500, 'FETCH_ERROR')
)
}

return ApiResponses.success(openingHours || [])
}

Response Format

// Opening hours response
[
{
id: "hours-uuid-1",
company_id: "company-uuid",
day_of_week: 0, // Sunday
open_time: null,
close_time: null,
is_closed: true
},
{
id: "hours-uuid-2",
company_id: "company-uuid",
day_of_week: 1, // Monday
open_time: "09:00",
close_time: "17:30",
is_closed: false
},
// ... continues for all 7 days
]

Opening Hours Structure

Day of Week Mapping

ValueDayNotes
0SundayTypically closed
1MondayStart of UK business week
2Tuesday
3Wednesday
4Thursday
5Friday
6SaturdayOften reduced hours

Database Schema

// Source: src/types/database.types.ts - company_opening_hours table
interface CompanyOpeningHours {
id: string
company_id: string
day_of_week: number // 0-6 (Sunday-Saturday)
open_time: string | null // HH:MM format (24-hour)
close_time: string | null // HH:MM format (24-hour)
is_closed: boolean // True if closed on this day
created_at: string
updated_at: string
}

Time Format

All times use 24-hour format (UK standard):

FormatExampleDescription
HH:MM09:009:00 AM
HH:MM17:305:30 PM
HH:MM00:00Midnight
HH:MM23:59End of day

Updating Opening Hours

Update API

Endpoint: PUT /api/companies/[companyId]/hours

Permission Required

Only System Administrators can update opening hours.

// Source: src/app/api/companies/[companyId]/hours/route.ts:62-169
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ companyId: string }> }
) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()

// System Admin only
if (getAuthRole(user) !== 'SYSTEM_ADMIN') {
return ApiResponses.forbidden('Forbidden - System Admin access required')
}

const resolvedParams = await params
const body = await request.json()

// Validate opening hours array
const validationResult = parseWithErrors(openingHoursArraySchema, body.hours)

if (!validationResult.success) {
return ApiResponses.validationError(validationResult.errors)
}

// Delete existing hours
const { error: deleteError } = await supabase
.from('company_opening_hours')
.delete()
.eq('company_id', resolvedParams.companyId)

if (deleteError) {
return ApiResponses.error(
new DatabaseError('Failed to update opening hours', 500, 'DELETE_ERROR')
)
}

// Insert new hours
const hoursToInsert = validationResult.data.map(hour => ({
...hour,
company_id: resolvedParams.companyId
}))

const { data: newHours, error: insertError } = await supabase
.from('company_opening_hours')
.insert(hoursToInsert)
.select()
.order('day_of_week')

if (insertError) {
return ApiResponses.error(
new DatabaseError('Failed to save opening hours', 500, 'INSERT_ERROR')
)
}

return ApiResponses.success(newHours)
}

Validation Schema

// Source: src/lib/validation/company.schemas.ts:87-125
export const dayOfWeekSchema = z.number().int().min(0).max(6)

export const timeSchema = z.string().regex(
/^([01]\d|2[0-3]):([0-5]\d)$/,
'Invalid time format. Use HH:MM (24-hour)'
)

export const openingHoursSchema = z.object({
day_of_week: dayOfWeekSchema,
open_time: timeSchema.nullable(),
close_time: timeSchema.nullable(),
is_closed: z.boolean().default(false)
}).refine(
(data) => {
// If not closed, both times required
if (!data.is_closed) {
return data.open_time !== null && data.close_time !== null
}
return true
},
{ message: 'Open and close times required for open days' }
).refine(
(data) => {
// Close time must be after open time
if (data.open_time && data.close_time) {
return data.close_time > data.open_time
}
return true
},
{ message: 'Close time must be after open time' }
)

export const openingHoursArraySchema = z.array(openingHoursSchema).length(7)

Example Update Request

// Update opening hours
const response = await fetch(`/api/companies/${companyId}/hours`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
hours: [
{ day_of_week: 0, is_closed: true, open_time: null, close_time: null },
{ day_of_week: 1, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 2, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 3, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 4, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 5, is_closed: false, open_time: "09:00", close_time: "17:00" },
{ day_of_week: 6, is_closed: true, open_time: null, close_time: null }
]
})
})

const data = await response.json()

Common Schedule Patterns

Standard UK Business Hours

// Monday-Friday 9:00-17:30, closed weekends
const standardHours = [
{ day_of_week: 0, is_closed: true, open_time: null, close_time: null },
{ day_of_week: 1, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 2, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 3, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 4, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 5, is_closed: false, open_time: "09:00", close_time: "17:30" },
{ day_of_week: 6, is_closed: true, open_time: null, close_time: null }
]

Extended Hours (GP Practice)

// Early morning and evening appointments
const extendedHours = [
{ day_of_week: 0, is_closed: true, open_time: null, close_time: null },
{ day_of_week: 1, is_closed: false, open_time: "07:30", close_time: "19:00" },
{ day_of_week: 2, is_closed: false, open_time: "07:30", close_time: "19:00" },
{ day_of_week: 3, is_closed: false, open_time: "07:30", close_time: "18:30" },
{ day_of_week: 4, is_closed: false, open_time: "07:30", close_time: "19:00" },
{ day_of_week: 5, is_closed: false, open_time: "07:30", close_time: "18:00" },
{ day_of_week: 6, is_closed: false, open_time: "09:00", close_time: "12:00" }
]

Restaurant Hours

// Split service with lunch and dinner
const restaurantHours = [
{ day_of_week: 0, is_closed: false, open_time: "12:00", close_time: "22:00" },
{ day_of_week: 1, is_closed: true, open_time: null, close_time: null },
{ day_of_week: 2, is_closed: false, open_time: "12:00", close_time: "22:00" },
{ day_of_week: 3, is_closed: false, open_time: "12:00", close_time: "22:00" },
{ day_of_week: 4, is_closed: false, open_time: "12:00", close_time: "22:00" },
{ day_of_week: 5, is_closed: false, open_time: "12:00", close_time: "23:00" },
{ day_of_week: 6, is_closed: false, open_time: "12:00", close_time: "23:00" }
]

Integration with Scheduling

Shift Validation

Opening hours affect shift scheduling:

// Shifts validated against opening hours
const validateShiftAgainstHours = async (shift: Shift, companyId: string) => {
const { data: hours } = await supabase
.from('company_opening_hours')
.select('*')
.eq('company_id', companyId)
.eq('day_of_week', new Date(shift.date).getDay())
.single()

// Check if business is open
if (hours.is_closed) {
return {
valid: false,
error: 'Cannot schedule shifts on closed days'
}
}

// Validate shift times against business hours
if (shift.start_time < hours.open_time) {
return {
valid: false,
error: `Shift cannot start before business opens at ${hours.open_time}`
}
}

if (shift.end_time > hours.close_time) {
return {
valid: false,
error: `Shift cannot end after business closes at ${hours.close_time}`
}
}

return { valid: true }
}

Calendar Display

Opening hours are displayed on the scheduling calendar:

// Visual indication of business hours
const renderBusinessHours = (day: Date, hours: OpeningHours[]) => {
const dayHours = hours.find(h => h.day_of_week === day.getDay())

if (dayHours?.is_closed) {
return <div className="bg-gray-100 text-gray-400">Closed</div>
}

return (
<div className="text-sm text-secondary-text">
{dayHours?.open_time} - {dayHours?.close_time}
</div>
)
}

Timezone Handling

UK Timezone Rules

// Source: src/lib/utils/timezone.ts
export const UK_TIMEZONE = 'Europe/London'

// Opening hours are stored without timezone
// but displayed in UK local time
const formatOpeningHours = (hours: OpeningHours) => {
const openMoment = moment.tz(hours.open_time, 'HH:mm', UK_TIMEZONE)
const closeMoment = moment.tz(hours.close_time, 'HH:mm', UK_TIMEZONE)

return {
open: openMoment.format('HH:mm'),
close: closeMoment.format('HH:mm')
}
}

DST Considerations

PeriodUK TimeNotes
GMTNov - MarNo adjustment
BSTMar - Oct+1 hour

Opening hours are stored as local times and do not require DST adjustment.


Display Components

Weekly Schedule Display

// Opening hours display component
const OpeningHoursDisplay = ({ hours }: { hours: OpeningHours[] }) => {
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']

return (
<div className="space-y-2">
{days.map((day, index) => {
const dayHours = hours.find(h => h.day_of_week === index)

return (
<div key={day} className="flex justify-between">
<span className="font-medium">{day}</span>
{dayHours?.is_closed ? (
<span className="text-secondary-text">Closed</span>
) : (
<span>{dayHours?.open_time} - {dayHours?.close_time}</span>
)}
</div>
)
})}
</div>
)
}

Compact Display

// Compact single-line summary
const OpeningHoursSummary = ({ hours }: { hours: OpeningHours[] }) => {
const weekdayHours = hours.find(h => h.day_of_week === 1) // Monday

if (!weekdayHours || weekdayHours.is_closed) {
return <span>Hours vary</span>
}

return (
<span>
Mon-Fri: {weekdayHours.open_time} - {weekdayHours.close_time}
</span>
)
}

Best Practices

For Configuring Hours

  1. Be Accurate - Ensure times match actual operating hours
  2. Include Buffer - Allow time for setup/closedown
  3. Consider Patterns - Use industry-appropriate schedules
  4. Review Regularly - Update when hours change

For Scheduling Integration

  1. Respect Hours - Don't schedule outside business hours
  2. Early Start - Consider staff arriving before opening
  3. Late Finish - Allow for closing procedures
  4. Special Days - Account for bank holidays separately

Validation Rules

Time Format

RuleRequirement
FormatHH:MM (24-hour)
Hours00-23
Minutes00-59
OrderClose > Open

Business Rules

RuleDescription
Complete WeekAll 7 days must be defined
Open Day RequirementsNon-closed days need both times
Closed Day RequirementsClosed days can have null times
Time OrderClose time must be after open time

Troubleshooting

Common Issues

IssueCauseSolution
Validation failsWrong time formatUse HH:MM (e.g., 09:00)
Cannot saveMissing daysEnsure all 7 days defined
Times rejectedClose before openSwap open and close times
Update failsInsufficient permissionsContact System Admin

Error Messages

ErrorMeaningAction
"Invalid time format"Not HH:MM formatUse 24-hour format
"Close time must be after open time"Logic errorCorrect time order
"Open and close times required"Missing times for open dayAdd both times
"Forbidden"Not System AdminContact administrator


Source Files:

  • src/app/api/companies/[companyId]/hours/route.ts - Opening hours API
  • src/lib/validation/company.schemas.ts - Validation schemas
  • src/types/database.types.ts - Database type definitions
  • src/lib/utils/timezone.ts - Timezone utilities