Skip to main content

Creating Shifts

This guide covers all methods for creating shifts in Shyfts, including the shift creation form, field requirements, and conflict handling.


Creating a Shift

Method 1: Click on Calendar

  1. Navigate to DashboardScheduling
  2. Click on an empty time slot in a room row
  3. The shift creation modal opens with pre-filled defaults
// Source: src/components/calendar/RoomTimeGridCalendar.tsx:230-250
const handleSlotClick = (roomId: string, hour: number) => {
const startTime = `${hour.toString().padStart(2, '0')}:00`
const endTime = `${(hour + 1).toString().padStart(2, '0')}:00`

setCreateModalState({
isOpen: true,
defaultRoomId: roomId,
defaultDate: currentDate,
defaultTime: startTime
})
}

Method 2: Add Shift Button

  1. Click the + Add Shift button in the calendar header
  2. The shift creation modal opens without pre-filled values

Shift Creation Form

Form Fields

// Source: src/components/calendar/ShiftCreateModal.tsx:45-52
const [formData, setFormData] = useState({
staff_id: '',
room_id: normalizedDefaultRoom,
start_time: defaultTime || '09:00',
end_time: '17:00',
shift_type: 'REGULAR' as ShiftType,
notes: ''
})
FieldTypeRequiredDescription
Staff MemberSelect✅ YesStaff member to assign
RoomSelect✅ YesRoom/location for shift
DateDate✅ YesShift date (DD/MM/YYYY)
Start TimeTime✅ YesShift start (24-hour)
End TimeTime✅ YesShift end (24-hour)
Shift TypeSelect✅ YesType of shift
NotesText❌ NoOptional notes

Staff Selection

Active Staff List

Only active staff members appear in the selection dropdown:

// Source: src/components/calendar/ShiftCreateModal.tsx:98-115
const { data: staffData } = await supabase
.from('staff')
.select(`
id,
first_name,
last_name,
staff_roles (
id,
name,
color
)
`)
.eq('company_id', companyId)
.eq('is_active', true)
.order('first_name')

Staff Display Format

┌─────────────────────────────────────┐
│ Staff Member ▼ │
├─────────────────────────────────────┤
│ Select staff member... │
│ ● John Smith (Nurse) │
│ ● Sarah Johnson (Doctor) │
│ ● Mike Brown (Receptionist) │
└─────────────────────────────────────┘

Room Selection

Active Rooms

Rooms are filtered by active status:

// Source: src/components/calendar/ShiftCreateModal.tsx:117-130
const { data: roomsData } = await supabase
.from('rooms')
.select(`
id,
name,
room_types (
id,
name
)
`)
.eq('company_id', companyId)
.eq('is_active', true)
.order('name')

Room Display

PropertyShown
Room name✅ Yes
Room type✅ Yes (in parentheses)
Capacity status❌ No (shown via conflicts)

Shift Types

Available Types

// Source: src/components/calendar/ShiftCreateModal.tsx:376-383
<select>
<option value="REGULAR">Regular</option>
<option value="OVERTIME">Overtime</option>
<option value="ON_CALL">On Call</option>
<option value="EMERGENCY">Emergency</option>
<option value="TRAINING">Training</option>
<option value="MEETING">Meeting</option>
</select>
TypeDescriptionTypical Use
REGULARStandard working shiftDaily scheduled work
OVERTIMEAdditional hours beyond normalExtra coverage needs
ON_CALLAvailable if neededEmergency standby
EMERGENCYUrgent coverageLast-minute requirements
TRAININGTraining/developmentStaff development
MEETINGScheduled meetingsTeam meetings, reviews

Time Selection

Time Format

All times use 24-hour format (HH:mm):

// Source: src/components/calendar/ShiftCreateModal.tsx:340-365
<input
type="time"
value={formData.start_time}
onChange={(e) => setFormData(prev => ({
...prev,
start_time: e.target.value
}))}
className="form-input"
/>

Time Validation

RuleValidation
End after startEnd time must be after start time
Within operating hoursWarning if outside company hours
Minimum duration15 minutes minimum

Default Values

FieldDefaultSource
Start timeClicked hour or 09:00Calendar click position
End timeStart + 8 hours or 17:00Calculated from start
DateCurrent calendar dateCalendar state

Conflict Detection

Real-Time Checking

Conflicts are checked as you fill the form:

// Source: src/components/calendar/ShiftCreateModal.tsx:180-210
useEffect(() => {
const checkConflicts = async () => {
if (!formData.staff_id || !formData.room_id) return

const conflicts = await schedulingService.detectConflicts({
staff_id: formData.staff_id,
room_id: formData.room_id,
date: selectedDate,
start_time: formData.start_time,
end_time: formData.end_time
})

setDetectedConflicts(conflicts)
}

checkConflicts()
}, [formData.staff_id, formData.room_id, formData.start_time, formData.end_time])

Conflict Display

// Source: src/components/calendar/ShiftCreateModal.tsx:425-455
{detectedConflicts && detectedConflicts.hasConflicts && (
<div className="mt-4 p-4 rounded-lg bg-red-500/10 border border-red-500/20">
<div className="flex items-center gap-2 text-red-400 font-medium">
<AlertTriangle className="h-5 w-5" />
Scheduling Conflicts Detected
</div>
<ul className="mt-2 space-y-1 text-sm text-red-300">
{detectedConflicts.conflicts.map((conflict, index) => (
<li key={index}>{conflict.message}</li>
))}
</ul>
</div>
)}

Conflict Severity

SeverityActionVisual
CRITICALBlocks submissionRed warning
HIGHWarning, can proceedOrange warning
MEDIUMInformationalYellow warning
LOWNotice onlyGrey notice

For detailed conflict information, see Conflict Resolution.


Form Submission

Submit Handler

// Source: src/components/calendar/ShiftCreateModal.tsx:250-300
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSubmitting(true)

try {
// Combine date and time into full timestamp
const startDateTime = combineUKDateAndTimeToUTC(
selectedDate,
formData.start_time
)
const endDateTime = combineUKDateAndTimeToUTC(
selectedDate,
formData.end_time
)

const result = await createShift({
staff_id: formData.staff_id,
room_id: formData.room_id,
start_time: startDateTime,
end_time: endDateTime,
shift_type: formData.shift_type,
notes: formData.notes || null
})

if (result) {
onClose()
refreshShifts()
}
} catch (error) {
setError('Failed to create shift')
} finally {
setIsSubmitting(false)
}
}

Success Flow

  1. Form validation passes
  2. Conflicts checked (warnings shown if any)
  3. API call to create shift
  4. Modal closes on success
  5. Calendar refreshes to show new shift

Error Handling

// Source: src/components/calendar/ShiftCreateModal.tsx:465-475
{error && (
<div className="p-4 rounded-lg bg-red-500/10 border border-red-500/20">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}

API Endpoint

Create Shift

Endpoint: POST /api/scheduling/shifts

Request Body:

{
"staff_id": "uuid",
"room_id": "uuid",
"start_time": "2025-01-13T09:00:00Z",
"end_time": "2025-01-13T17:00:00Z",
"shift_type": "REGULAR",
"notes": "Optional notes"
}

Response:

{
"success": true,
"data": {
"id": "uuid",
"staff_id": "uuid",
"room_id": "uuid",
"start_time": "2025-01-13T09:00:00Z",
"end_time": "2025-01-13T17:00:00Z",
"shift_type": "REGULAR",
"status": "SCHEDULED",
"created_at": "2025-01-13T08:30:00Z"
}
}

Context Integration

SchedulingContext

Shift creation uses the SchedulingContext:

// Source: src/contexts/SchedulingContext.tsx:85-120
const createShift = async (data: CreateShiftData): Promise<boolean> => {
try {
setLoading(true)

const response = await fetch('/api/scheduling/shifts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...data,
company_id: companyId
})
})

if (!response.ok) {
throw new Error('Failed to create shift')
}

await refreshShifts()
return true
} catch (error) {
console.error('Create shift error:', error)
return false
} finally {
setLoading(false)
}
}

Form Validation

Required Fields

FieldValidation
Staff memberMust be selected
RoomMust be selected
Start timeMust be valid time
End timeMust be after start time
Shift typeMust be valid type

Time Validation

// Source: src/components/calendar/ShiftCreateModal.tsx:230-245
const validateTimes = () => {
const start = moment(formData.start_time, 'HH:mm')
const end = moment(formData.end_time, 'HH:mm')

if (end.isSameOrBefore(start)) {
setError('End time must be after start time')
return false
}

return true
}

Quick Creation Tips

Keyboard Shortcuts

KeyAction
EnterSubmit form (when focused on submit button)
EscapeClose modal without saving
TabNavigate between fields

Best Practices

  1. Check conflicts first - Review any warnings before submitting
  2. Use appropriate shift type - Helps with reporting and analytics
  3. Add notes for context - Useful for handovers and reference
  4. Verify times - Double-check 24-hour format


Source Files:

  • src/components/calendar/ShiftCreateModal.tsx - Shift creation form
  • src/contexts/SchedulingContext.tsx - Shift state management
  • src/services/schedulingService.ts - Scheduling business logic
  • src/app/api/scheduling/shifts/route.ts - API endpoint