Skip to main content

Editing Staff

This guide covers all methods for updating staff profiles, including the enhanced 4-tab form system, field updates, and data validation.


Accessing Staff Edit

From Staff List

  1. Navigate to DashboardStaff Management
  2. Click on a staff member row to view details
  3. Click the Edit Profile button
// Source: src/components/staff/StaffDetail.tsx:94-100
<button
onClick={() => onEdit(staff)}
className="form-button flex items-center gap-2"
>
<Edit className="w-5 h-5" />
Edit Profile
</button>

From Staff Detail View

The staff detail view displays all information across 5 tabs:

// Source: src/components/staff/StaffDetail.tsx:58-65
const tabs: Array<{ id: TabType; label: string; icon: React.ReactNode }> = [
{ id: 'overview', label: 'Overview', icon: <User className="w-4 h-4" /> },
{ id: 'preferences', label: 'Working Preferences', icon: <Briefcase className="w-4 h-4" /> },
{ id: 'skills', label: 'Skills & Certifications', icon: <Award className="w-4 h-4" /> },
{ id: 'availability', label: 'Availability', icon: <CalendarDays className="w-4 h-4" /> },
{ id: 'constraints', label: 'Constraints', icon: <AlertCircle className="w-4 h-4" /> },
]

Enhanced Staff Form

4-Tab Edit Form

The staff edit form uses the same enhanced form component as creation:

// Source: src/components/staff/EnhancedStaffForm.tsx:477-483
<Tabs defaultValue="basic" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="basic">Basic Info</TabsTrigger>
<TabsTrigger value="preferences">Working Preferences</TabsTrigger>
<TabsTrigger value="skills">Skills & Certifications</TabsTrigger>
<TabsTrigger value="availability">Availability</TabsTrigger>
</TabsList>

Edit Mode Detection

// Source: src/components/staff/EnhancedStaffForm.tsx:362-365
if (staff) {
// Update existing staff using enhanced API
const result = await updateStaffWithCompleteData(companyId, staff.id, {
// ... update data
})
}

Tab 1: Basic Information

Editable Fields

FieldTypeRequiredValidation
First NameText✅ YesMin 1 character
Last NameText✅ YesMin 1 character
EmailEmail✅ YesValid email format
PhoneTel✅ YesValid phone format
AddressText✅ YesMax 200 characters
Date of BirthDate✅ YesYYYY-MM-DD format
RoleSelect✅ YesValid role UUID
Start DateDate✅ YesYYYY-MM-DD format
StatusSelect✅ YesActive/Inactive
Contract HoursNumber✅ Yes0-80 hours
Pay TypeSelect❌ Nohourly/weekly/monthly/annual
Hourly RateCurrency❌ NoIf pay type is hourly
Salary AmountCurrency❌ NoIf pay type is salary
Employment TypeSelect✅ Yesfull-time/part-time/contract/temp
NotesTextarea❌ NoFree text

Phone Validation

// Source: src/components/staff/EnhancedStaffForm.tsx:29
const phoneRegex = /^\+?[1-9][\d\s\-().]{0,20}$/

Date Validation

// Source: src/components/staff/EnhancedStaffForm.tsx:44-50
date_of_birth: z.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date of birth must use YYYY-MM-DD format'),

employment_start_date: z.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Employment start date must use YYYY-MM-DD format'),

Tab 2: Working Preferences

Editable Preferences

// Source: src/components/staff/EnhancedStaffForm.tsx:66-78
working_preferences: z.object({
preferred_start_time: z.string().optional(),
preferred_end_time: z.string().optional(),
min_hours_per_week: z.number().min(0).max(168).optional(),
max_hours_per_week: z.number().min(0).max(168).optional(),
min_hours_per_day: z.number().min(0).max(24).optional(),
max_hours_per_day: z.number().min(0).max(24).optional(),
max_consecutive_days: z.number().min(1).max(14).optional(),
min_days_off_between: z.number().min(0).max(7).optional(),
weekend_availability: z.boolean().optional(),
overtime_willing: z.boolean().optional(),
preferred_shift_types: z.array(z.string()).optional()
}).optional()

Preference Fields

PreferenceTypeRangeDescription
Preferred StartTime00:00-23:59Ideal shift start time
Preferred EndTime00:00-23:59Ideal shift end time
Min Hours/WeekNumber0-168Minimum weekly hours
Max Hours/WeekNumber0-168Maximum weekly hours
Min Hours/DayNumber0-24Minimum daily hours
Max Hours/DayNumber0-24Maximum daily hours
Max Consecutive DaysNumber1-14Maximum days in a row
Days Off BetweenNumber0-7Required rest days
Weekend AvailableBooleanYes/NoCan work weekends
Overtime WillingBooleanYes/NoWill work overtime
Shift TypesMulti-select-Preferred shift types

Default Values

// Source: src/components/staff/EnhancedStaffForm.tsx:199-210
working_preferences: {
preferred_start_time: '09:00',
preferred_end_time: '17:00',
min_hours_per_week: 20,
max_hours_per_week: 40,
min_hours_per_day: 4,
max_hours_per_day: 12,
max_consecutive_days: 5,
min_days_off_between: 1,
weekend_availability: false,
overtime_willing: false,
preferred_shift_types: []
}

Tab 3: Skills & Certifications

Skill Entry Structure

// Source: src/components/staff/EnhancedStaffForm.tsx:81-93
skills_detailed: z.array(z.object({
skill_name: z.string(),
skill_category: z.string(),
proficiency_level: z.number().min(1).max(10).optional(),
certification_number: z.string().optional(),
certification_issuer: z.string().optional(),
last_training_date: z.string().optional(),
certification_expiry: z.string().optional(),
certification_required: z.boolean().optional(),
training_provider: z.string().optional(),
next_training_due: z.string().optional(),
is_active: z.boolean().optional()
})).optional()

Skill Fields

FieldTypeDescription
Skill NameTextName of the skill
CategoryTextSkill category
ProficiencyNumber 1-10Proficiency level
Cert NumberTextCertification ID
Cert IssuerTextIssuing organisation
Last TrainingDateMost recent training
Cert ExpiryDateWhen certification expires
RequiredBooleanIf certification is required
ProviderTextTraining provider name
Next DueDateNext training date
ActiveBooleanIf skill is current

Tab 4: Availability

Availability Entry Structure

// Source: src/components/staff/EnhancedStaffForm.tsx:96-106
availability: z.array(z.object({
day_of_week: z.number().min(0).max(6),
start_time: z.string(),
end_time: z.string(),
is_available: z.boolean().optional(),
break_duration: z.string().optional(), // INTERVAL format: '00:30:00'
availability_pattern: z.string().optional(),
availability_priority: z.number().min(1).max(10).optional(),
time_slot_type: z.string().optional(),
availability_notes: z.string().optional()
})).optional()

Day of Week Values

ValueDay
0Sunday
1Monday
2Tuesday
3Wednesday
4Thursday
5Friday
6Saturday

Update Process

Form Submission

// Source: src/components/staff/EnhancedStaffForm.tsx:364-388
const result = await updateStaffWithCompleteData(companyId, staff.id, {
basicInfo: {
id: staff.id,
company_id: companyId,
first_name: data.first_name,
last_name: data.last_name,
email: data.email,
phone: data.phone,
address: data.address,
date_of_birth: data.date_of_birth,
role_id: data.role_id,
employment_start_date: data.employment_start_date,
employment_end_date: data.employment_end_date ? data.employment_end_date : null,
notes: data.notes || null,
requires_clock_in: data.requires_clock_in,
contract_hours: data.contract_hours,
hourly_rate: data.hourly_rate ?? null,
pay_type: data.pay_type ?? null,
salary_amount: data.salary_amount ?? null,
employment_type: data.employment_type
},
working_preferences: data.working_preferences,
skills_detailed: data.skills_detailed,
availability: data.availability
})

Loading Existing Data

When editing, the form loads complete staff data:

// Source: src/components/staff/EnhancedStaffForm.tsx:256-262
async function loadEnhancedData() {
if (!staffData?.id || !companyId) return

try {
setLoadingEnhancedData(true)
const completeData = await getStaffWithFullDetails(staffData.id)
// ... populate form with complete data
}
}

API Endpoint

Update Staff

Endpoint: PUT /api/companies/[companyId]/staff/[staffId]

Request Body:

{
"basicInfo": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"phone": "+44 7123 456789",
"role_id": "uuid",
"employment_start_date": "2024-01-15",
"contract_hours": 40,
"employment_type": "full-time"
},
"working_preferences": {
"preferred_start_time": "09:00",
"preferred_end_time": "17:00",
"max_hours_per_week": 40
},
"skills_detailed": [],
"availability": []
}

Response:

{
"success": true,
"data": {
"id": "staff-uuid",
"firstName": "John",
"lastName": "Smith",
"email": "john.smith@example.com",
"updatedAt": "2025-01-13T10:30:00Z"
}
}

Pay Type Conditional Fields

Field Visibility Logic

// Source: src/components/staff/EnhancedStaffForm.tsx:223-230
useEffect(() => {
if (watchPayType === 'hourly') {
// Hourly pay: clear salary_amount
setValue('salary_amount', undefined)
} else if (['weekly', 'monthly', 'annual'].includes(watchPayType || '')) {
// Salary pay: clear hourly_rate
setValue('hourly_rate', undefined)
}
}, [watchPayType, setValue])

Pay Type Options

Pay TypeShows FieldFormat
HourlyHourly Rate£/hour
WeeklySalary Amount£/week
MonthlySalary Amount£/month
AnnualSalary Amount£/year

Validation

Form Validation Schema

The form uses Zod validation:

// Source: src/components/staff/EnhancedStaffForm.tsx:32-107
const enhancedStaffSchema = z.object({
first_name: z.string().min(1, 'First name is required'),
last_name: z.string().min(1, 'Last name is required'),
email: z.string().email('Invalid email address'),
phone: z.string()
.regex(phoneRegex, 'Invalid phone number format')
.max(30, 'Phone number must not exceed 30 characters'),
address: z.string()
.min(1, 'Address is required')
.max(200, 'Address must not exceed 200 characters'),
// ... additional fields
})

Error Display

// Source: src/components/staff/EnhancedStaffForm.tsx:499-501
{errors.first_name && (
<p className="text-sm text-red-400 mt-1">{errors.first_name.message}</p>
)}

Error Handling

Submission Errors

// Source: src/components/staff/EnhancedStaffForm.tsx:430-432
} catch (err) {
console.error('Error saving staff:', err)
setError(err instanceof Error ? err.message : 'Failed to save staff member')
}

Error Display

// Source: src/components/staff/EnhancedStaffForm.tsx:464-467
{error && (
<Alert variant="destructive">
{error}
</Alert>
)}

Loading States

Role Loading

// Source: src/components/staff/EnhancedStaffForm.tsx:575-578
<option value="" disabled>
{loadingRoles ? 'Loading roles...' : 'Select a role...'}
</option>

Data Loading

// Source: src/components/staff/EnhancedStaffForm.tsx:470-474
{loadingEnhancedData && staff && (
<Alert>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Loading complete staff information...
</Alert>
)}

Best Practices

When Editing Staff

  1. Review current data - Check existing values before making changes
  2. Update one section at a time - Complete each tab before moving on
  3. Validate email changes - Ensure new email is correct (affects login)
  4. Check role permissions - Role changes affect system access
  5. Document changes in notes - Record significant updates

Data Integrity

  1. Never change staff ID - This is system-generated
  2. Preserve historical data - Use notes for change tracking
  3. Verify certifications - Check expiry dates when updating
  4. Update availability carefully - Affects scheduling suggestions


Source Files:

  • src/components/staff/EnhancedStaffForm.tsx - Enhanced staff form
  • src/components/staff/StaffDetail.tsx - Staff detail view
  • src/utils/api/staff.ts - Staff API utilities
  • src/lib/adapters/staff.adapter.ts - Staff data adapter