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