Staff Documents
This guide covers document management for staff members, including uploading files, document types, verification workflows, and compliance tracking.
Overview
Document Management Purpose
Staff documents in Shyfts support:
- Compliance - Track required certifications and qualifications
- HR Records - Maintain employment contracts and policies
- Training - Monitor professional development documents
- Tax & Payroll - Store P45, P60, and tax documents
- Identity - Keep verified ID documents on file
Two Document Systems
Shyfts has two document tables for different purposes:
| Table | Purpose | Scope |
|---|---|---|
| documents | Company-wide documents | Shared policies, templates |
| staff_documents | Staff-specific files | Individual staff records |
Staff Documents Database Structure
// Source: src/types/database.types.ts:2512-2560
staff_documents: {
Row: {
id: string
company_id: string
staff_id: string
name: string
document_type: string
file_path: string
file_size: number
mime_type: string
expiry_date: string | null
upload_date: string | null
is_confidential: boolean | null
notes: string | null
created_at: string | null
updated_at: string | null
}
}
Company Documents Structure
// Source: src/types/database.types.ts:650-701
documents: {
Row: {
id: string
company_id: string
staff_id: string | null
document_name: string
document_type: string
file_url: string
file_type: string | null
issue_date: string | null
expiry_date: string | null
is_verified: boolean | null
verified_by: string | null
verified_at: string | null
notes: string | null
created_at: string | null
updated_at: string | null
}
}
Accessing Staff Documents
From Staff Portal
Staff members access their documents via the Staff Portal:
Location: /staff/documents
// Source: src/app/staff/documents/page.tsx:35-70
export default function StaffDocumentsPage() {
const { user, loading } = useAuth()
const [documents, setDocuments] = useState<Document[]>([])
const [selectedCategory, setSelectedCategory] = useState<string>('all')
useEffect(() => {
const fetchDocuments = async () => {
try {
const response = await fetch('/api/staff/documents')
const data = await response.json()
setDocuments(data.documents || [])
} catch (error) {
console.error('Error fetching documents:', error)
}
}
if (user) {
fetchDocuments()
}
}, [user])
}
From Company Manager View
Company Managers access staff documents through the staff detail view:
- Navigate to Dashboard → Staff Management
- Click on a staff member
- Select the Documents tab (if available)
Document Types
Standard Categories
| Category | Examples | Retention |
|---|---|---|
| Contracts | Employment contract, amendments | Duration of employment + 6 years |
| Tax | P45, P60, P11D | 6 years |
| Identity | Passport, driving licence, visa | While employed |
| Certifications | Professional qualifications | While valid |
| Training | Course certificates, CPD records | 3 years |
| Policies | Signed company policies | Duration of employment |
| Performance | Reviews, appraisals | 3 years |
| Medical | Fit notes, occupational health | Duration of employment |
Category Filtering
// Source: src/app/staff/documents/page.tsx:95-120
const categories = [
{ id: 'all', label: 'All Documents' },
{ id: 'contract', label: 'Contracts' },
{ id: 'tax', label: 'Tax Documents' },
{ id: 'certification', label: 'Certifications' },
{ id: 'training', label: 'Training' },
{ id: 'policy', label: 'Policies' },
{ id: 'other', label: 'Other' }
]
const filteredDocuments = selectedCategory === 'all'
? documents
: documents.filter(doc => doc.document_type === selectedCategory)
Uploading Documents
Upload Process
- Navigate to the staff member's profile
- Go to the Documents section
- Click Upload Document
- Select the file from your device
- Choose the document type
- Add any relevant notes
- Set expiry date if applicable
- Click Upload
File Validation
| Property | Limit |
|---|---|
| Maximum size | 10 MB |
| Allowed types | PDF, PNG, JPG, JPEG, DOC, DOCX |
| File name | Max 255 characters |
Upload API
Endpoint: POST /api/documents
Request (multipart/form-data):
file: [binary file data]
staff_id: "uuid"
document_type: "contract"
document_name: "Employment Contract 2025"
expiry_date: "2026-01-13" (optional)
notes: "Updated contract with salary revision" (optional)
Response:
{
"success": true,
"data": {
"id": "doc-uuid",
"document_name": "Employment Contract 2025",
"document_type": "contract",
"file_url": "https://storage.supabase.co/...",
"created_at": "2025-01-13T10:00:00Z"
}
}
Document Verification
Verification Workflow
For documents requiring verification (e.g., ID, certifications):
- Upload - Document uploaded by staff or manager
- Pending - Document awaits verification
- Review - Manager reviews document authenticity
- Verified/Rejected - Status updated with notes
Verification Fields
| Field | Purpose |
|---|---|
| is_verified | Boolean verification status |
| verified_by | Staff ID of verifier |
| verified_at | Timestamp of verification |
| notes | Verification notes/comments |
Verification API
Endpoint: PATCH /api/documents/[id]
Request Body:
{
"is_verified": true,
"verified_by": "manager-staff-id",
"notes": "Original document sighted and verified"
}
Viewing Documents
Document List Display
// Source: src/app/staff/documents/page.tsx:160-200
{documents.map((doc) => (
<div key={doc.id} className="card-glass p-4 rounded-lg">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<FileText className="w-8 h-8 text-accent-coral" />
<div>
<h3 className="font-medium text-primary-text">
{doc.document_name}
</h3>
<p className="text-sm text-secondary-text">
{doc.document_type} • Uploaded {formatDate(doc.created_at)}
</p>
</div>
</div>
<div className="flex gap-2">
<button
onClick={() => handleDownload(doc.id)}
className="btn-icon"
>
<Download className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(doc.id)}
className="btn-icon text-red-400"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
Document Information Displayed
| Field | Format |
|---|---|
| Name | Document name |
| Type | Category badge |
| Upload Date | DD/MM/YYYY |
| Expiry Date | DD/MM/YYYY (if set) |
| Status | Verified/Pending/Expired badge |
| Actions | Download, Delete buttons |
Downloading Documents
Download API
Endpoint: GET /api/documents/download/[id]
// Source: src/app/api/documents/download/[id]/route.ts:15-45
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const supabase = await createServerClient()
// Fetch document metadata
const { data: document, error } = await supabase
.from('documents')
.select('*')
.eq('id', params.id)
.single()
if (error || !document) {
return NextResponse.json(
{ error: 'Document not found' },
{ status: 404 }
)
}
// Generate signed URL for download
const { data: signedUrl } = await supabase.storage
.from('documents')
.createSignedUrl(document.file_path, 60)
return NextResponse.json({ url: signedUrl.signedUrl })
}
Signed URL Security
Downloads use time-limited signed URLs:
| Property | Value |
|---|---|
| URL Validity | 60 seconds |
| Access Control | RLS enforced |
| Audit Trail | Download logged |
Expiry Tracking
Expiry Date Management
Documents with expiry dates are tracked for compliance:
// Check for expiring documents
const expiringDocs = documents.filter(doc => {
if (!doc.expiry_date) return false
const expiryDate = new Date(doc.expiry_date)
const warningDate = new Date()
warningDate.setDate(warningDate.getDate() + 30)
return expiryDate <= warningDate
})
Expiry Status Display
| Status | Condition | Display |
|---|---|---|
| Valid | > 30 days until expiry | Green badge |
| Expiring Soon | ≤ 30 days until expiry | Amber badge |
| Expired | Past expiry date | Red badge |
| No Expiry | No expiry date set | No badge |
Expiry Notifications
When documents are expiring:
- Dashboard Alert - Banner notification for managers
- Staff Notification - Reminder to upload renewed document
- Email Reminder - Automated email 30 days before expiry
Confidential Documents
Confidentiality Flag
The is_confidential field marks sensitive documents:
// Source: src/types/database.types.ts:2521
is_confidential: boolean | null
Access Control
| Document Type | Who Can View |
|---|---|
| Standard | Staff member, Company Manager |
| Confidential | Company Manager only |
Marking as Confidential
When uploading or editing:
{
"is_confidential": true,
"notes": "Contains salary information"
}
Deleting Documents
Soft Delete
Documents are soft-deleted to maintain audit trails:
Endpoint: DELETE /api/documents/[id]
// Source: src/app/api/documents/[id]/route.ts:45-70
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const supabase = await createServerClient()
// Verify ownership/permissions
const { data: { user } } = await supabase.auth.getUser()
// Delete document record
const { error } = await supabase
.from('documents')
.delete()
.eq('id', params.id)
if (error) {
return NextResponse.json(
{ error: 'Failed to delete document' },
{ status: 500 }
)
}
// Also delete from storage
await supabase.storage
.from('documents')
.remove([document.file_path])
return NextResponse.json({ success: true })
}
Deletion Confirmation
const handleDelete = async (docId: string) => {
if (!confirm('Are you sure you want to delete this document? This cannot be undone.')) {
return
}
try {
await fetch(`/api/documents/${docId}`, { method: 'DELETE' })
// Refresh document list
refreshDocuments()
} catch (error) {
console.error('Error deleting document:', error)
}
}
Storage Configuration
Supabase Storage
Documents are stored in Supabase Storage:
| Bucket | Purpose |
|---|---|
| documents | All document files |
File Path Structure
documents/
├── {company_id}/
│ ├── company/ # Company-wide documents
│ │ └── policies/
│ └── staff/
│ └── {staff_id}/ # Staff-specific documents
│ ├── contracts/
│ ├── certifications/
│ └── tax/
Storage Policies
| Policy | Rule |
|---|---|
| Upload | Authenticated users only |
| Download | Same company or owner |
| Delete | Manager or document owner |
Compliance Requirements
GDPR Considerations
| Requirement | Implementation |
|---|---|
| Right to Access | Staff can view all their documents |
| Right to Erasure | Delete function available |
| Data Minimisation | Only necessary documents stored |
| Security | Encrypted storage, signed URLs |
Retention Periods
| Document Type | Retention Period |
|---|---|
| Employment contracts | Duration + 6 years |
| Tax records | 6 years |
| Training records | 3 years minimum |
| Disciplinary records | Duration of warning |
| Medical records | Duration of employment |
Automatic Cleanup
Documents past retention can be flagged for review:
-- Find documents past retention
SELECT * FROM staff_documents
WHERE created_at < NOW() - INTERVAL '6 years'
AND document_type = 'tax'
API Reference
List Staff Documents
Endpoint: GET /api/staff/documents
Response:
{
"success": true,
"documents": [
{
"id": "doc-uuid",
"document_name": "Employment Contract",
"document_type": "contract",
"file_url": "...",
"expiry_date": null,
"is_verified": true,
"created_at": "2025-01-13T10:00:00Z"
}
]
}
Get Single Document
Endpoint: GET /api/documents/[id]
Update Document
Endpoint: PATCH /api/documents/[id]
Request Body:
{
"document_name": "Updated Name",
"expiry_date": "2026-01-13",
"notes": "Updated notes"
}
Best Practices
Document Management
- Consistent naming - Use clear, descriptive names
- Correct categorisation - Choose appropriate document type
- Set expiry dates - For all time-limited documents
- Regular audits - Review document completeness quarterly
- Prompt verification - Verify ID documents promptly
Security
- Mark confidential - Flag sensitive documents appropriately
- Limit access - Only grant access to those who need it
- Audit downloads - Monitor document access patterns
- Secure disposal - Follow retention policies
Compliance
- Collect required docs - Ensure all legally required documents are on file
- Track expiry - Monitor certification expiry dates
- Maintain records - Keep verification audit trail
- Follow retention - Delete documents per retention policy
Troubleshooting
Upload Issues
| Issue | Solution |
|---|---|
| File too large | Compress or split document |
| Invalid file type | Convert to PDF |
| Upload failed | Check network connection, retry |
| Permission denied | Verify user has upload rights |
Access Issues
| Issue | Solution |
|---|---|
| Document not found | Check document ID and permissions |
| Download timeout | Signed URL may have expired, retry |
| Cannot view confidential | Request manager access |
Related Documentation
- Adding Staff - Create new staff
- Editing Staff - Update staff profiles
- Staff Roles - Role permissions
Source Files:
src/types/database.types.ts- Document type definitionssrc/app/staff/documents/page.tsx- Staff documents pagesrc/app/api/documents/route.ts- Document upload APIsrc/app/api/documents/[id]/route.ts- Document management APIsrc/app/api/documents/download/[id]/route.ts- Download API