Time Tracking & Resource Management Guide
Last Updated: 2026-01-12 Status: Implemented Plan Reference: 044-time-tracking-resource-management.md, 064-time-tracking-enhancement.md, 088-time-tracking-improvement.md
Overview
The Time Tracking & Resource Management system provides comprehensive time tracking with project allocation, team capacity planning, resource utilization analytics, and timesheet management. It enables accurate project costing, team workload visibility, and billable hours tracking for invoicing.
Table of Contents
- Accessing Time Tracking
- Time Entries
- Bulk Time Entry
- Timer Widget
- Mobile Timer Widget
- Cross-Device Timer Sync
- Keyboard Shortcuts
- Timesheets
- Timesheet Approval Workflow
- Billable Time & Invoice Integration
- Productivity Reports
- Resource Planning
- Technical Architecture
- Related Features
Accessing Time Tracking
Navigation
| Access Point | Location | URL | Role |
|---|---|---|---|
| Time Entries | Admin sidebar | /admin/time | Admin |
| Timesheets | Admin sidebar | /admin/timesheets | Admin |
| Resource Planning | Admin sidebar | /admin/resources | Admin |
| Team Capacity | Resources page | /admin/resources/capacity | Admin |
| Tasks | Admin sidebar | /admin/tasks | Admin |
Permissions
| Action | Admin | Client User |
|---|---|---|
| Log time entries | ✅ | ❌ |
| View own entries | ✅ | ❌ |
| Submit timesheet | ✅ | ❌ |
| Approve timesheets | ✅ (Managers) | ❌ |
| View resource planning | ✅ | ❌ |
| Manage allocations | ✅ | ❌ |
Time Entries
Creating Manual Entries
- Navigate to Admin → Time Entries
- Click "New Entry"
- Fill in entry details:
- Project: Select the project
- Task (optional): Select specific task
- Date: Entry date
- Duration: Time spent in hours/minutes
- Description: What you worked on
- Billable: Mark if billable
- Click "Save Entry"
Editing Entries
- Navigate to Admin → Time Entries
- Click on an entry row
- Modify fields as needed
- Click "Update Entry"
Entry Status Workflow
Pending → Submitted → Approved
↘ Rejected (back to Pending)
| Status | Description |
|---|---|
pending | Entry created, not yet submitted |
approved | Approved by manager |
rejected | Rejected, needs revision |
Filtering Entries
- By Project: Filter to specific project
- By Date Range: Select start and end dates
- By Status: Filter by approval status
- By Billable: Filter billable/non-billable
Bulk Time Entry
The bulk entry feature allows logging multiple time entries at once, ideal for end-of-week time entry or catching up on missed days.
Accessing Bulk Entry
- Navigate to Admin → Time Entries
- Click "Bulk Entry" button
- Or use keyboard shortcut Alt + B
- Or navigate directly to
/admin/time/bulk
Using Bulk Entry
- The form starts with one empty row
- Fill in:
- Date: Entry date
- Project: Select from dropdown
- Hours: Duration (0.25 - 24)
- Description: What you worked on
- Billable: Toggle checkbox
- Click "Add Row" for more entries
- Click "Add 5 Rows" to add multiple at once
- Click "Save All Entries" when complete
Features
- Smart Defaults: New rows inherit date and project from previous row
- Total Calculation: Running total of hours shown in header and footer
- Validation: Each entry is validated before saving
- Limit: Maximum 50 entries per submission
Validation Rules
| Field | Rules |
|---|---|
| Date | Required, valid date, not future |
| Project | Required, must exist |
| Hours | Required, 0.25 - 24 |
| Description | Optional, max 500 characters |
API Endpoint
POST /admin/time/bulk
{
"entries": [
{
"date": "2026-01-12",
"project_id": 1,
"hours": 2.5,
"description": "Feature development",
"is_billable": true
}
]
}
Timer Widget
Starting a Timer
- Click the Timer icon in the navigation bar
- Select Project (required)
- Select Task (optional)
- Add Description (optional)
- Click "Start Timer"
Timer Controls
| Action | Button | Description |
|---|---|---|
| Start | ▶️ Play | Begin tracking time |
| Stop | ⏹️ Stop | Stop and save entry |
| Discard | 🗑️ Trash | Cancel without saving |
Timer Features
- Real-time Display: Shows elapsed time
- One Active Timer: Only one timer per user at a time
- Auto-Save: Stopping creates a time entry
- Persistent: Timer continues across page navigation
API Endpoints
# Start timer
POST /admin/time/timer/start
{
"project_id": 1,
"task_id": null,
"description": "Working on feature",
"is_billable": true
}
# Stop timer
POST /admin/time/timer/stop
# Get running timer
GET /admin/time/timer/running
Mobile Timer Widget
The mobile timer is a floating widget for quick time tracking on any page.
Accessing the Widget
The mobile timer appears as a floating button in the bottom-right corner of admin pages. It shows:
- Clock Icon: When no timer running
- Elapsed Time: When timer is active (e.g., "02:34:15")
Using the Mobile Timer
- Click the floating button to expand the widget
- Select Project from dropdown (required)
- Select Task if available (optional)
- Enter Description for the work
- Toggle Billable checkbox
- Click "Start" to begin tracking
Controls
| Button | Action |
|---|---|
| Start | Begin tracking time |
| Pause | Pause timer without saving |
| Stop & Save | Stop and create time entry |
| Discard | Cancel without saving |
Features
- Persistent State: Timer continues across page navigation
- LocalStorage Backup: State saved locally for recovery
- Minimum Duration Warning: Confirms if entry is less than 1 minute
- Recent Entries: Shows last 5 time entries
- Auto-Load Tasks: Tasks load when project is selected
Component Usage
<x-mobile-timer :projects="$projects" />
Including in Layout
Add to your layout file to enable on all pages:
@auth
<x-mobile-timer :projects="$timerProjects" />
@endauth
Cross-Device Timer Sync
The timer synchronization feature allows users to seamlessly switch between devices while tracking time.
How It Works
- Device Registration: Each device is assigned a unique device ID
- State Synchronization: Timer state is synced via WebSocket events
- Conflict Resolution: Server maintains authoritative timer state
- Offline Support: Local elapsed time cached for offline use
Timer State Properties
| Property | Description |
|---|---|
elapsed_seconds | Total time elapsed on timer |
is_running | Whether timer is currently active |
device_id | Device that last modified timer |
last_sync_at | Last synchronization timestamp |
Real-Time Events
The TimerUpdated event broadcasts timer changes:
// Event channels
private-timer.{userId}
// Event payload
{
"action": "started|paused|resumed|stopped",
"timer": { ... timer state ... },
"device_id": "device-uuid"
}
API Endpoints
# Sync timer state
POST /admin/time/timer/sync
{
"device_id": "uuid",
"elapsed_seconds": 3600,
"is_running": true
}
# Pause timer (cross-device)
POST /admin/time/timer/pause
# Resume timer (cross-device)
POST /admin/time/timer/resume
# Resume from existing entry
POST /admin/time/timer/resume-from-entry
{
"time_entry_id": 123
}
Keyboard Shortcuts
Time tracking includes keyboard shortcuts for quick access to common actions.
Available Shortcuts
| Shortcut | Action | Description |
|---|---|---|
| Alt + T | Toggle Timer | Start or pause the active timer |
| Alt + S | Stop Timer | Stop timer and save the entry |
| Alt + N | New Entry | Open new manual time entry form |
| Alt + E | Expand/Collapse | Toggle mobile timer widget |
| Alt + B | Bulk Entry | Open bulk time entry page |
How It Works
Shortcuts are handled by resources/js/time-tracking/keyboard-shortcuts.js:
import { initTimeTrackingShortcuts } from './time-tracking/keyboard-shortcuts';
// Initialize with default handlers
initTimeTrackingShortcuts();
// Or with custom handlers
initTimeTrackingShortcuts({
onToggle: () => customToggle(),
onStop: () => customStop(),
onNewEntry: () => openCustomModal(),
});
Input Field Behavior
Keyboard shortcuts are disabled when typing in:
- Text inputs
- Textareas
- Select dropdowns
- Content-editable elements
This prevents accidental navigation while entering data.
Showing Help
Display a shortcuts help modal:
import { showShortcutsHelp } from './time-tracking/keyboard-shortcuts';
// Show modal with all available shortcuts
showShortcutsHelp();
Timesheets
Weekly View
- Navigate to Admin → Timesheets
- View current week's entries by day
- See daily and weekly totals
- Navigate weeks with arrow buttons
Timesheet Components
| Section | Content |
|---|---|
| Day Grid | Entries organized by day |
| Project Summary | Time grouped by project |
| Totals | Weekly hours (total/billable) |
| Status | Draft/Submitted/Approved |
Submitting Timesheets
- Complete all entries for the week
- Review totals and accuracy
- Click "Submit for Approval"
- Status changes to "Submitted"
Approving Timesheets (Managers)
- Navigate to Admin → Timesheets → Pending
- Review submitted timesheet
- Click "Approve" or "Reject"
- If rejecting, provide reason
Timesheet Status
| Status | Description |
|---|---|
draft | In progress, not submitted |
submitted | Awaiting approval |
approved | Approved by manager |
rejected | Needs revision |
changes_requested | Manager requested changes |
Timesheet Approval Workflow
The enhanced approval workflow provides granular control over timesheet review.
Approval Actions
| Action | Description | Available When |
|---|---|---|
| Approve | Accept timesheet with optional notes | Submitted, Changes Requested |
| Reject | Reject with required reason | Submitted, Changes Requested |
| Request Changes | Ask for specific modifications | Submitted, Changes Requested |
| Reopen | Return rejected timesheet to draft | Rejected (owner only) |
Requesting Changes
Managers can request specific changes without fully rejecting:
- Navigate to Admin → Timesheets → Pending
- Click on a timesheet to review
- Click "Request Changes"
- Enter message describing needed changes
- Timesheet status changes to
changes_requested - Employee receives notification with message
Change Request Tracking
Each change request is stored with:
- Requested By: Manager who requested
- Message: Description of changes needed
- Changes: Specific entries to modify (optional)
- Status: pending, addressed, dismissed
- Addressed At: When changes were made
Approval with Notes
Managers can add notes when approving:
- Click "Approve" on timesheet
- Enter optional notes for the employee
- Notes are stored and visible to employee
- Approval notification includes notes
Notifications
| Event | Recipient | Channels |
|---|---|---|
| Submitted | Managers | Email, Database |
| Approved | Employee | Email, Database |
| Rejected | Employee | Email, Database |
| Changes Requested | Employee | Email, Database |
Bulk Approval
For efficiency, managers can approve multiple timesheets:
- Navigate to Admin → Timesheets → All
- Select timesheets with checkboxes
- Click "Bulk Approve"
- All selected timesheets are approved
Routes
GET /admin/timesheets # User's timesheet
GET /admin/timesheets/pending # Pending approvals
GET /admin/timesheets/all # All timesheets (admin)
GET /admin/timesheets/statistics # Weekly statistics
GET /admin/timesheets/{timesheet} # Review timesheet
POST /admin/timesheets/{timesheet}/submit # Submit for approval
POST /admin/timesheets/{timesheet}/approve # Approve
POST /admin/timesheets/{timesheet}/reject # Reject
POST /admin/timesheets/{timesheet}/request-changes # Request changes
POST /admin/timesheets/{timesheet}/reopen # Reopen rejected
POST /admin/timesheets/bulk-approve # Bulk approve
Billable Time & Invoice Integration
The billable time integration streamlines creating invoices from tracked time.
Finding Unbilled Time
- Navigate to Admin → Clients → [Client] → Invoices
- Click "View Unbilled Time"
- See all approved, unbilled entries for client
Creating Invoice from Time
- Click "Create Invoice from Time"
- Select time entries to include
- Preview generated invoice items
- Confirm and create invoice
- Time entries automatically marked as billed
Invoice Preview
Before creating, preview shows:
- Grouped entries by project
- Total hours per project
- Calculated amounts using hourly rates
- Overall invoice total
Unbilled Summary
View summary of unbilled time per client:
$summary = $billableTimeService->getUnbilledSummary($clientId);
// Returns:
// - total_entries
// - total_hours
// - total_amount
// - by_project (breakdown)
Unlinking Time from Invoice
If needed, time entries can be unlinked:
- Navigate to the invoice
- Find linked time entries
- Click "Unlink" to remove association
- Entries return to unbilled status
API Methods
// Get billable entries for client
$entries = $billableTimeService->getBillableEntries($clientId, $filters);
// Create invoice from time entries
$invoice = $billableTimeService->createInvoiceFromTime($client, $entryIds);
// Preview invoice items
$items = $billableTimeService->previewInvoiceItems($entryIds);
// Unlink entries from invoice
$billableTimeService->unlinkFromInvoice($invoice);
Productivity Reports
The TimeReportsService provides comprehensive analytics on time tracking data.
User Productivity Report
View individual user productivity metrics for any date range:
Navigate to: Admin → Time → Reports → Productivity
Metrics Included:
- Total hours worked
- Billable vs non-billable hours
- Billable percentage
- Average hours per day
- Working days count
- Hours by project
- Daily breakdown
API Endpoint:
GET /admin/time/reports/productivity?start_date=2026-01-01&end_date=2026-01-31&user_id=1
Service Method:
$service = app(TimeReportsService::class);
$report = $service->getUserProductivity($userId, $startDate, $endDate);
// Returns:
// [
// 'total_hours' => 160,
// 'billable_hours' => 128,
// 'non_billable_hours' => 32,
// 'billable_percentage' => 80.0,
// 'average_per_day' => 8.0,
// 'working_days' => 20,
// 'by_project' => [...],
// 'daily_breakdown' => [...],
// ]
Team Capacity Report
View team utilization and availability for any week:
Navigate to: Admin → Time → Reports → Capacity
Metrics Per User:
- Logged hours
- Capacity hours (default 40)
- Utilization percentage
- Remaining hours
- Over-capacity flag
- Billable hours
API Endpoint:
GET /admin/time/reports/capacity?week=2026-01-06
Service Method:
$capacity = $service->getTeamCapacity($weekDate);
// Returns Collection with each team member's:
// - user_id, user_name
// - logged_hours, capacity_hours
// - utilization (percentage)
// - remaining_hours
// - is_over_capacity
// - billable_hours
Project Budget Utilization
Track project hours against budget:
$utilization = $service->getProjectBudgetUtilization($projectId);
// Returns:
// [
// 'project_name' => 'Website Redesign',
// 'total_hours' => 50,
// 'budget_hours' => 100,
// 'utilization' => 50.0,
// 'remaining_hours' => 50,
// 'is_over_budget' => false,
// 'by_user' => [...],
// 'by_status' => [...],
// ]
Timesheet Summary
Get weekly summary for a user:
$summary = $service->getTimesheetSummary($userId, $weekDate);
// Returns:
// [
// 'week_start' => '2026-01-06',
// 'week_end' => '2026-01-12',
// 'total_hours' => 40,
// 'billable_hours' => 32,
// 'pending_approval' => 5,
// 'approved' => 15,
// 'by_day' => [...],
// 'by_project' => [...],
// ]
Billable Summary for Invoicing
Get unbilled time for a client:
$summary = $service->getBillableSummary($clientId, $startDate, $endDate);
// Returns:
// [
// 'period_start' => '2026-01-01',
// 'period_end' => '2026-01-31',
// 'total_hours' => 80,
// 'total_amount' => 12000,
// 'entries_count' => 45,
// 'by_project' => [
// [
// 'project_id' => 1,
// 'project_name' => 'API Development',
// 'hours' => 40,
// 'hourly_rate' => 150,
// 'amount' => 6000,
// ],
// ...
// ],
// ]
Resource Planning
Project Allocations
Allocate team members to projects with scheduled hours.
Creating Allocation:
- Navigate to Admin → Resources → Allocations
- Click "New Allocation"
- Select User and Project
- Set Start Date and End Date (optional)
- Enter Hours per Week
- Set Hourly Rate (optional)
- Add Role description (optional)
- Click "Save"
Team Capacity View
View team utilization and availability.
| Metric | Description |
|---|---|
| Total Capacity | Weekly hours available (default 40) |
| Allocated | Hours assigned to projects |
| Logged | Actual hours tracked |
| Billable | Billable hours tracked |
| Time Off | Vacation/sick hours |
| Available | Capacity - Allocated - Time Off |
Utilization Report
See team efficiency metrics:
Utilization % = (Billable Hours / Total Capacity) × 100
Report Features:
- Filter by date range
- View by team member
- Export to CSV
- Trend visualization
Finding Available Resources
- Navigate to Admin → Resources → Capacity
- Select Week to view
- Enter Hours Needed
- View team members with availability
- Create allocation from results
Tasks
Creating Tasks
- Navigate to Admin → Tasks
- Click "New Task"
- Fill in task details:
- Name: Task title
- Project: Parent project
- Assigned To: Team member
- Description: Task details
- Estimated Hours: Time estimate
- Priority: Low/Medium/High/Urgent
- Due Date: Target completion
Task Status
| Status | Description |
|---|---|
pending | Not started |
in_progress | Currently working |
completed | Finished |
cancelled | Cancelled |
Task Time Tracking
- Time entries can link to tasks
- Estimated Hours: Initial estimate
- Actual Hours: Sum of time entries
- Variance: Actual - Estimated
Time Off Management
Requesting Time Off
- Navigate to Admin → Time Off
- Click "Request Time Off"
- Select Type: Vacation, Sick, Personal, Holiday
- Enter Start Date and End Date
- Specify Hours (or calculates automatically)
- Add Notes (optional)
- Click "Submit Request"
Time Off Types
| Type | Description |
|---|---|
vacation | Planned time off |
sick | Illness |
personal | Personal days |
holiday | Company holidays |
Approval Workflow
Pending → Approved
↘ Rejected
Billable Time & Invoicing
Marking Time as Billable
- Default billable setting per project
- Can override per entry
- Tracks hourly rate from allocation or user default
Linking to Invoices
- Navigate to client's invoices
- Click "Create Invoice"
- Select "Include Unbilled Time"
- Choose entries to include
- Time entries marked as billed
Viewing Unbilled Time
- Navigate to Clients → [Client] → Invoices
- Click "Unbilled Time"
- View all approved, unbilled entries
- Filter by project or date range
Technical Architecture
Models
Location: app/Models/
| Model | Table | Purpose |
|---|---|---|
TimeEntry | time_entries | Individual time records |
RunningTimer | running_timers | Active timers |
Timesheet | timesheets | Weekly time summaries |
TimesheetChangeRequest | timesheet_change_requests | Approval change requests |
Task | tasks | Project tasks |
ProjectAllocation | project_allocations | Resource assignments |
TeamCapacity | team_capacity | Weekly capacity records |
TimeOffRequest | time_off_requests | Time off records |
Services
Location: app/Services/TimeTracking/
// TimeTrackingService - Core time tracking operations
class TimeTrackingService
{
public function startTimer(User $user, array $data): RunningTimer;
public function stopTimer(User $user): ?TimeEntry;
public function getRunningTimer(User $user): ?RunningTimer;
public function createManualEntry(User $user, array $data): TimeEntry;
public function getTimesheet(User $user, Carbon $weekStart): Timesheet;
public function getWeeklyReport(User $user, Carbon $weekStart): array;
public function getUnbilledTime(int $clientId): Collection;
public function markAsBilled(array $entryIds, int $invoiceId): int;
}
// TimerSyncService - Cross-device timer synchronization
class TimerSyncService
{
public function syncTimer(User $user, array $state): RunningTimer;
public function getTimerState(User $user): ?array;
public function startTimer(User $user, array $data): RunningTimer;
public function pauseTimer(User $user): RunningTimer;
public function resumeTimer(User $user): RunningTimer;
public function stopTimer(User $user): TimeEntry;
public function resumeFromEntry(User $user, TimeEntry $entry): RunningTimer;
}
// TimesheetApprovalService - Approval workflow management
class TimesheetApprovalService
{
public function submitForApproval(Timesheet $timesheet): Timesheet;
public function approve(Timesheet $timesheet, User $approver, ?string $notes): Timesheet;
public function reject(Timesheet $timesheet, User $rejector, string $reason): Timesheet;
public function requestChanges(Timesheet $timesheet, User $requester, array $changes, ?string $message): TimesheetChangeRequest;
public function addressChangesAndResubmit(Timesheet $timesheet, TimesheetChangeRequest $request): Timesheet;
public function getPendingTimesheets(): Collection;
public function getApprovalHistory(Timesheet $timesheet): Collection;
}
// BillableTimeService - Invoice integration
class BillableTimeService
{
public function getBillableEntries(int $clientId, array $filters = []): Collection;
public function createInvoiceFromTime(Client $client, array $entryIds): Invoice;
public function getUnbilledSummary(int $clientId): array;
public function getClientsWithUnbilledTime(): Collection;
public function previewInvoiceItems(array $entryIds): array;
public function unlinkFromInvoice(Invoice $invoice): int;
}
// ResourcePlanningService - Team capacity management
class ResourcePlanningService
{
public function allocateToProject(User $user, Project $project, array $data);
public function updateCapacity(User $user, Carbon $weekStart): TeamCapacity;
public function getTeamCapacity(Carbon $weekStart): Collection;
public function getUtilizationReport(Carbon $start, Carbon $end): array;
public function findAvailableResources(Carbon $weekStart, int $hoursNeeded);
}
// TimeReportsService - Productivity and capacity reporting
// Location: app/Services/TimeReportsService.php
class TimeReportsService
{
public function getUserProductivity(int $userId, Carbon $start, Carbon $end): array;
public function getProjectBudgetUtilization(int $projectId): array;
public function getTeamCapacity(Carbon $week): Collection;
public function getTimesheetSummary(int $userId, Carbon $week): array;
public function getBillableSummary(int $clientId, Carbon $start, Carbon $end): array;
}
Events
Location: app/Events/
| Event | Channel | Description |
|---|---|---|
TimerUpdated | private-timer.{userId} | Timer state changed |
Controllers
Location: app/Http/Controllers/Admin/
| Controller | Routes | Purpose |
|---|---|---|
TimeEntryController | /admin/time/* | Time entry CRUD |
TimesheetController | /admin/timesheets/* | Timesheet management |
ResourceController | /admin/resources/* | Resource planning |
TaskController | /admin/tasks/* | Task management |
TimeOffController | /admin/time-off/* | Time off requests |
Routes
// Time entries
Route::resource('time', TimeEntryController::class);
Route::post('time/timer/start', [TimeEntryController::class, 'startTimer']);
Route::post('time/timer/stop', [TimeEntryController::class, 'stopTimer']);
Route::get('time/timer/running', [TimeEntryController::class, 'runningTimer']);
Route::post('time/timer/update', [TimeEntryController::class, 'updateTimer']);
Route::post('time/timer/discard', [TimeEntryController::class, 'discardTimer']);
// Bulk Entry
Route::get('time/bulk', [TimeEntryController::class, 'bulkCreate']);
Route::post('time/bulk', [TimeEntryController::class, 'bulkStore']);
Route::post('time/bulk-approve', [TimeEntryController::class, 'bulkApprove']);
Route::post('time/bulk-reject', [TimeEntryController::class, 'bulkReject']);
// Reports
Route::get('time/reports/productivity', [TimeEntryController::class, 'productivityReport']);
Route::get('time/reports/capacity', [TimeEntryController::class, 'capacityReport']);
// AJAX Endpoints
Route::get('time/recent', [TimeEntryController::class, 'recentEntries']);
Route::get('time/projects/{project}/tasks', [TimeEntryController::class, 'projectTasks']);
// Timesheets
Route::get('timesheets', [TimesheetController::class, 'index']);
Route::post('timesheets/{timesheet}/submit', [TimesheetController::class, 'submit']);
Route::get('timesheets/pending', [TimesheetController::class, 'pending']);
Route::post('timesheets/{timesheet}/approve', [TimesheetController::class, 'approve']);
Route::post('timesheets/{timesheet}/reject', [TimesheetController::class, 'reject']);
// Resources
Route::get('resources', [ResourceController::class, 'index']);
Route::get('resources/capacity', [ResourceController::class, 'capacity']);
Route::get('resources/utilization', [ResourceController::class, 'utilization']);
Route::resource('allocations', AllocationController::class);
// Tasks and Time Off
Route::resource('tasks', TaskController::class);
Route::resource('time-off', TimeOffController::class);
Database Tables
Table: time_entries
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
user_id | bigint | User FK |
project_id | bigint | Project FK |
task_id | bigint | Task FK (nullable) |
description | string | Work description |
entry_date | date | Date of work |
duration_minutes | int | Time in minutes |
is_billable | boolean | Billable flag |
is_billed | boolean | Billed flag |
is_locked | boolean | Locked for editing |
invoice_id | bigint | Invoice FK (nullable) |
hourly_rate | decimal | Rate for billing |
status | string | pending/approved/rejected |
Table: running_timers
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
user_id | bigint | User FK |
project_id | bigint | Project FK |
task_id | bigint | Task FK (nullable) |
description | string | Work description |
started_at | timestamp | Timer start time |
elapsed_seconds | int | Accumulated time |
is_running | boolean | Currently active |
device_id | string | Originating device |
last_sync_at | timestamp | Last sync time |
Table: timesheets
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
user_id | bigint | User FK |
week_start | date | Week start date |
week_end | date | Week end date |
total_hours | int | Total hours |
billable_hours | int | Billable hours |
status | string | draft/submitted/approved/rejected/changes_requested |
approved_by | bigint | Approver FK (nullable) |
approved_at | timestamp | Approval timestamp |
approval_notes | text | Approver notes |
rejected_by | bigint | Rejector FK (nullable) |
rejected_at | timestamp | Rejection timestamp |
rejection_reason | text | Rejection reason |
Table: timesheet_change_requests
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
timesheet_id | bigint | Timesheet FK |
requested_by | bigint | Requester FK |
changes | json | Requested changes |
message | text | Request message |
status | string | pending/addressed/dismissed |
addressed_at | timestamp | When addressed |
Best Practices
For Time Tracking
- Track in real-time using the timer when possible
- Add descriptions for each entry
- Submit timesheets weekly for approval
- Review entries before submitting
- Use tasks to categorize work
For Resource Planning
- Allocate before projects start
- Update allocations when scope changes
- Monitor utilization weekly
- Plan for time off in advance
- Review capacity before committing
Troubleshooting
| Issue | Solution |
|---|---|
| Timer not starting | Check if another timer is running |
| Can't edit entry | Entry may be approved, contact manager |
| Wrong project hours | Verify task assignments |
| Utilization too high | Check allocation overlaps |
| Missing billable time | Verify entries marked as billable |
Related Features
Dependencies
| Feature | Relationship |
|---|---|
| Project Management | Projects and tasks |
| Invoicing | Billable time to invoices |
| Authorization | Approval permissions |
Complementary Features
| Feature | Description |
|---|---|
| Analytics | Time analytics |
| Reports | Time reports |
| Admin Dashboard | Time overview |
See Also
- Project Management - Project context
- Invoicing - Billing integration
- Analytics - Time analytics