Skip to main content

Submitting Timesheet

This guide covers how to submit your timesheet entries for manager approval, including submission requirements, the approval process, and handling rejected entries.


Overview

What is Timesheet Submission?

Submitting your timesheet sends your recorded hours to your manager for approval:

  1. Review Entries - Check all entries are accurate
  2. Submit for Approval - Send to manager
  3. Await Review - Manager reviews and approves/rejects
  4. Payroll Processing - Approved hours processed for payroll

Submission Workflow

Draft → Review → Submit → Manager Review → Approved/Rejected
↘ (If rejected) → Edit → Resubmit

Pre-Submission Requirements

Before You Submit

Ensure all entries meet these requirements:

RequirementCheck
Complete entriesAll have clock in AND clock out
Breaks recordedBreak time entered for 6+ hour shifts
No overlapsEntries don't overlap each other
Within date limitsNot older than 90 days
Accurate timesHours reflect actual work

Validation Checks

// Source: src/app/api/staff/timesheet/submit/route.ts:65-110
// Pre-submission validation
const validateForSubmission = (entries: TimeEntry[]): ValidationResult => {
const errors: string[] = []
const warnings: string[] = []

entries.forEach(entry => {
// Check clock out exists
if (!entry.clock_out_time) {
errors.push(`Entry ${entry.id}: Missing clock out time`)
}

// Check for unusually long shifts
if (entry.total_hours && entry.total_hours > 12) {
warnings.push(`Entry ${entry.id}: Shift exceeds 12 hours`)
}

// Check break for long shifts
if (entry.total_hours >= 6 && !entry.break_duration) {
warnings.push(`Entry ${entry.id}: No break recorded for 6+ hour shift`)
}
})

return {
valid: errors.length === 0,
errors,
warnings
}
}

Submission Process

Step-by-Step Submission

  1. Navigate to Timesheet view
  2. Select the period to submit (typically current week)
  3. Review all entries for accuracy
  4. Click Submit for Approval
  5. Review the submission summary
  6. Confirm submission

Submission API

Endpoint: POST /api/staff/timesheet/submit

// Source: src/app/api/staff/timesheet/submit/route.ts:25-50
const submitTimesheetSchema = z.object({
entry_ids: z.array(z.string().uuid()).min(1, 'At least one entry required'),
submission_date: z.string().datetime().optional(),
notes: z.string().max(500).optional()
})

Submit Request Example

// Submit timesheet entries
const submitTimesheet = async (entryIds: string[], notes?: string) => {
const response = await fetch('/api/staff/timesheet/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
entry_ids: entryIds,
notes: notes || 'Weekly timesheet submission'
})
})

const data = await response.json()

if (data.success) {
console.log(`Submitted ${data.data.submitted_count} entries`)
}

return data
}

Response Format

// Submission response
{
success: true,
data: {
submitted_count: 5,
total_hours: 40.0,
period: {
start_date: "2025-01-13",
end_date: "2025-01-17"
},
entries: [
{
id: "entry-uuid-1",
status: "submitted",
submitted_at: "2025-01-18T10:00:00Z"
},
// ... more entries
]
}
}

Secure Submission (RPC)

Database Function

Timesheet submission uses a secure RPC function for data integrity:

// Source: src/app/api/staff/timesheet/submit/route.ts:150-200
// Secure RPC call for submission
const { data, error } = await supabase.rpc('submit_timesheet', {
p_staff_id: staffId,
p_entry_ids: entryIds,
p_submission_notes: notes
})

RPC Function Benefits

BenefitDescription
AtomicityAll entries submitted together or none
ValidationServer-side validation enforced
Audit TrailSubmission timestamp recorded
SecurityRLS policies enforced

Automatic Hours Staff

Auto-Generated Entries

For staff with automatic hours calculation:

// Source: src/app/api/staff/timesheet/submit/route.ts:220-280
// Auto-generate entries from scheduled shifts
const generateAutoEntries = async (staffId: string, dateRange: DateRange) => {
// Fetch confirmed shifts without time entries
const { data: shifts } = await supabase
.from('shifts')
.select('*')
.eq('staff_id', staffId)
.eq('status', 'confirmed')
.gte('start_time', dateRange.start)
.lte('end_time', dateRange.end)

// Create time entries from shifts
const entries = shifts.map(shift => ({
staff_id: staffId,
clock_in_time: shift.start_time,
clock_out_time: shift.end_time,
break_duration: calculateAutoBreak(shift),
shift_id: shift.id,
status: 'draft',
notes: 'Auto-generated from scheduled shift'
}))

return entries
}

Auto-Generation Process

  1. System identifies scheduled shifts
  2. Creates time entries matching shift times
  3. Applies automatic break rules
  4. Entries marked as draft for review
  5. Staff can adjust before submission

Submission Summary

Before Confirmation

The submission summary shows:

// Submission summary structure
interface SubmissionSummary {
entriesCount: number
totalHours: number
period: {
startDate: string
endDate: string
}
breakdown: {
regularHours: number
overtimeHours: number
breakMinutes: number
}
warnings: string[]
readyToSubmit: boolean
}

Summary Display

MetricValueNotes
Entries5Number of time entries
Total Hours42.5Sum of all hours
Regular Hours40.0Up to threshold
Overtime2.5Above threshold
Period13/01 - 17/01Submission period

Submission Status

Entry Status Changes

When you submit:

BeforeAfter
DraftSubmitted
RejectedSubmitted (resubmission)

Status Indicators

StatusBadgeMeaning
DraftGreyNot submitted
SubmittedBlueAwaiting approval
ApprovedGreenConfirmed
RejectedRedNeeds correction

Manager Approval Process

What Happens After Submission

  1. Notification - Manager receives notification
  2. Review - Manager reviews entries
  3. Decision - Approve or reject
  4. Notification - You receive result notification

Approval Timeline

StageTypical Time
SubmissionImmediate
Manager notificationWithin minutes
Review1-3 business days
DecisionUsually same pay period

Handling Rejections

Why Entries Get Rejected

ReasonDescription
Inaccurate timesHours don't match records
Missing breaksRequired breaks not recorded
Overtime queryUnexpected overtime
DiscrepancyMismatch with scheduled shift
Documentation neededAdditional notes required

Rejection Notification

// Rejection response structure
{
entryId: "entry-uuid",
status: "rejected",
rejectedAt: "2025-01-19T14:00:00Z",
rejectedBy: "manager-uuid",
rejectionReason: "Please verify clock in time - building access shows 09:15"
}

Resubmission Process

  1. View rejected entries in Timesheet
  2. Read the rejection reason
  3. Click Edit on the rejected entry
  4. Make necessary corrections
  5. Add notes explaining the correction
  6. Submit again for approval
// Resubmit corrected entry
const resubmitEntry = async (entryId: string, corrections: Partial<TimeEntry>) => {
// Update the entry
await fetch(`/api/staff/timesheet/entries/${entryId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...corrections,
notes: 'Corrected as requested - verified times with access logs'
})
})

// Resubmit
await fetch('/api/staff/timesheet/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
entry_ids: [entryId],
notes: 'Resubmission after correction'
})
})
}

Bulk Submission

Submitting Multiple Entries

Submit all draft entries at once:

// Bulk submit all draft entries in period
const bulkSubmit = async (startDate: string, endDate: string) => {
// Fetch all draft entries
const response = await fetch(
`/api/staff/timesheet?start_date=${startDate}&end_date=${endDate}&status=draft`
)
const { data } = await response.json()

const draftEntryIds = data.entries.map((e: TimeEntry) => e.id)

if (draftEntryIds.length === 0) {
throw new Error('No draft entries to submit')
}

// Submit all
return fetch('/api/staff/timesheet/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
entry_ids: draftEntryIds,
notes: `Bulk submission for ${startDate} to ${endDate}`
})
})
}

Submission Limits

LimitValueReason
Max entries per submission100Performance
Max date range90 daysPolicy compliance
Minimum entries1At least one required

Submission Deadlines

Payroll Cut-Off

Most organisations have submission deadlines:

Deadline TypeExample
WeeklyFriday 17:00 for week's hours
Bi-weeklyAlternate Fridays
MonthlyLast day of month

Late Submission

Late submissions may:

  • Require manager approval for late entry
  • Delay payroll processing
  • Need additional documentation

Recall/Cancel Submission

Recalling Submitted Entries

If you need to make changes after submission:

// Recall submitted entry (before approval)
const recallEntry = async (entryId: string) => {
const response = await fetch(`/api/staff/timesheet/entries/${entryId}/recall`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
reason: 'Need to correct break time'
})
})

return response.json()
}

Recall Restrictions

StatusCan Recall?
Submitted✅ Yes
Approved❌ No - contact manager
RejectedN/A - already returned
DraftN/A - not submitted

Notifications

Submission Notifications

After submitting, notifications go to:

RecipientNotification
YouConfirmation of submission
ManagerAlert to review timesheet
PayrollCopy (if configured)

Email Notification Example

// Submission confirmation email
{
to: "staff@company.co.uk",
subject: "Timesheet Submitted - Week of 13/01/2025",
body: `
Your timesheet has been submitted for approval.

Period: 13/01/2025 - 17/01/2025
Total Hours: 40.0
Entries: 5

You will be notified when your manager reviews your timesheet.
`
}

Best Practices

For Successful Submission

  1. Submit Weekly - Don't let entries accumulate
  2. Review First - Check all entries before submitting
  3. Add Notes - Explain any unusual circumstances
  4. Submit Early - Don't wait until deadline
  5. Respond Quickly - Address rejections promptly

Submission Checklist

CheckStatus
☐ All entries have clock outRequired
☐ Breaks recorded for long shiftsRequired
☐ No overlapping entriesRequired
☐ Hours match scheduled shiftsRecommended
☐ Notes added where neededRecommended
☐ Total hours verifiedRecommended

Troubleshooting

Common Issues

IssueCauseSolution
"Cannot submit incomplete entries"Missing clock outComplete all entries
"No entries to submit"All already submittedCheck status filter
"Entries overlap"Time conflictAdjust entry times
"Submission failed"Server errorRetry submission

Error Messages

ErrorMeaningAction
"At least one entry required"No entries selectedSelect entries to submit
"Entry is already submitted"Duplicate submissionEntry already pending
"Entry validation failed"Missing required dataComplete entry details
"Submission deadline passed"Past cut-offContact manager


Source Files:

  • src/app/api/staff/timesheet/submit/route.ts - Submission API
  • src/app/api/staff/timesheet/entries/[entryId]/recall/route.ts - Recall API
  • src/components/staff/TimesheetSubmission.tsx - Submission UI
  • supabase/functions/submit_timesheet.sql - RPC function