The Employee Dashboard is your main entry point to the DTR System. It displays your current attendance status, allows you to select shifts and workplaces, and provides quick access to check-in/out functionality.
┌─────────────────────────────────────────────────────────────┐
│ EMPLOYEE DASHBOARD │
├─────────────────────────────────────────────────────────────┤
│ │
│ Current Shift | Shift Selector | Workplace | Workstation │
│ [Select] [Dropdown] [Dropdown] [Dropdown] │
│ │
│ Other Shifts [Toggle] [Check In] Button │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ATTENDANCE CALENDAR │
│ (Grid view of attendance records) │
│ │
└─────────────────────────────────────────────────────────────┘
The top section contains all controls for shift and workplace selection.
Before Check-In:
Current Shift
[Select Shift ▼] [Other Shifts Toggle] [Workplace ▼] [Workstation ▼]
[Check In Button]
After Check-In:
Current Shift
[Morning (08:00 - 17:00) - DISABLED] [Workplace (Disabled)]
[Check Out Button]
Key Feature: Once you check in, shift and workplace selectors are disabled to prevent accidental changes during your shift.
| State | When | Selectors | Check Button |
|---|---|---|---|
| Before Check-In | No active attendance | 🟢 Enabled | Check In |
| During Shift | Checked in | 🔴 Disabled | Check Out |
| After Check-Out | Shift ended | 🟢 Enabled | Check In |
A Shift defines your scheduled work hours for the day.
Examples:
Morning Shift: 08:00 AM - 05:00 PM (9 hours with 1 hour break)
Afternoon Shift: 02:00 PM - 11:00 PM (9 hours with 1 hour break)
Night Shift: 11:00 PM - 08:00 AM (9 hours with 1 hour break)
Step 1: Access Shift Dropdown
Before checking in, you'll see the shift selector dropdown:
Current Shift
[Morning (08:00 - 17:00) ▼]
├─ Morning (08:00 - 17:00)
├─ Afternoon (14:00 - 23:00)
└─ Night (23:00 - 08:00)
Step 2: Click Dropdown
Shows all shifts assigned to you today.
Step 3: Select Shift
Click desired shift to activate it.
✅ Shift Selected: Morning (08:00 - 17:00)
See additional shifts available:
┌─────────────────────────────────────┐
│ ASSIGN SHIFT │
├─────────────────────────────────────┤
│ │
│ [Shift Name | Time ▼] │
│ ├─ Morning | 08:00 - 17:00 │
│ ├─ Afternoon | 14:00 - 23:00 │
│ └─ Night | 23:00 - 08:00 │
│ │
│ [OK Button] │
│ │
└─────────────────────────────────────┘
A Workplace is a physical location where you work (office, warehouse, branch office, etc).
Example Workplaces:
• Main Office - 3rd Floor
• Branch Office - Makati
• Warehouse - Cavite
• Service Center - Quezon City
A Workstation is your specific desk/position within a workplace.
Example Workstations:
Main Office:
├─ Desk 5-A (your usual desk)
├─ Desk 5-B
├─ Conference Room 1
└─ Meeting Space
Remote Work:
├─ Home Office
└─ Client Site
Step 1: Click Workplace Dropdown
[Select Work Place ▼]
├─ Main Office - 3rd Floor
├─ Branch Office - Makati
├─ Warehouse - Cavite
└─ Service Center - Quezon City
Step 2: Choose Workplace
Select where you'll be working today.
✅ Selected: Main Office - 3rd Floor
Step 3: Workstation Selector Appears
After selecting workplace, workstation dropdown becomes available.
Step 1: Click Workstation Dropdown
[Select Work Stations ▼]
├─ Desk 5-A (Your usual desk)
├─ Desk 5-B
├─ Conference Room 1
└─ Meeting Space
Step 2: Choose Your Station
Select your desk or workspace for the day.
✅ Selected: Desk 5-A
If you've already checked in:
Workplace: [Main Office - 3rd Floor] (DISABLED - Gray)
Workstation: [Desk 5-A] (DISABLED - Gray)
Both fields are read-only to maintain consistency during your shift.
Before Check-In:
Status: NOT CHECKED IN
[Check In Button] (BLUE - Enabled)
Step 1: Click "Check In" Button
┌──────────────────────────────────┐
│ CONFIRMING CHECK-IN... │
│ │
│ Please wait... │
│ [Loading spinner] │
│ │
└──────────────────────────────────┘
Step 2: System Validates
Step 3: Check-In Confirmed
✅ CHECK-IN SUCCESSFUL
Time: 08:30 AM
Shift: Morning (08:00 - 17:00)
Workplace: Main Office - 3rd Floor
Workstation: Desk 5-A
Status: ✅ APPROVED
Step 4: Button Changes to Check Out
Status: CHECKED IN (8h 30m remaining)
[Check Out Button] (RED - Enabled)
Selectors Become Disabled:
[Morning (08:00 - 17:00)] (DISABLED)
[Main Office - 3rd Floor] (DISABLED)
[Desk 5-A] (DISABLED)
During Shift:
Status: CHECKED IN
Time Worked: 8 hours 45 minutes
[Check Out Button] (RED - Enabled)
Step 1: Click "Check Out" Button
┌──────────────────────────────────┐
│ CONFIRMING CHECK-OUT... │
│ │
│ Please wait... │
│ [Loading spinner] │
│ │
└──────────────────────────────────┘
Step 2: System Validates
Step 3: Check-Out Confirmed
✅ CHECK-OUT SUCCESSFUL
Check-In: 08:30 AM
Check-Out: 17:15 PM
Duration: 8h 45m
Status: ✅ APPROVED
Step 4: Dashboard Reset
Status: NOT CHECKED IN (Ready for next day)
[Check In Button] (BLUE - Re-enabled)
Shift Selector: 🟢 ENABLED
Workplace Selector: 🟢 ENABLED
Workstation Selector: 🟢 ENABLED
❌ ERROR: "Please select a shift first"
[Check In Button - DISABLED]
❌ ERROR: "Select a workplace before checking in"
Workplace dropdown shows red border
❌ ERROR: "Select a workstation before checking in"
Workstation dropdown shows red border
❌ ERROR: "Check-in failed. Please try again."
[Retry Button]
The calendar displays all your attendance records in a grid view, showing:
ATTENDANCE CALENDAR
════════════════════════════════════════════════════
DECEMBER 2025
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│ │ │ │ │ │ 1 │ 2 │
│ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
│ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ 16 │
│ 17 │ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │
│ 24 │ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │
│ 31 │ │ │ │ │ │ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┘
| Color | Meaning |
|---|---|
| 🟢 Green | Checked in (Approved) |
| 🟡 Yellow | Pending approval |
| 🔴 Red | Not checked in / Off day |
| ⬜ Gray | Future date (no data) |
Click any date to see details:
┌──────────────────────────────────┐
│ DECEMBER 4, 2025 │
├──────────────────────────────────┤
│ │
│ Status: ✅ CHECKED IN │
│ Shift: Morning (08:00 - 17:00) │
│ Workplace: Main Office - 3F │
│ Workstation: Desk 5-A │
│ │
│ Check-In: 08:30 AM │
│ Check-Out: 17:15 PM │
│ Duration: 8h 45m │
│ │
│ [Close] │
│ │
└──────────────────────────────────┘
✅ DO:
❌ DON'T:
Issue 1: "Check In button is disabled"
Solution:
Issue 2: "Cannot change shift while checked in"
Solution: This is intentional. You must check out first:
Issue 3: "Workstation dropdown is empty"
Solution:
Issue 4: "Check in failed error"
Solution:
Q: Can I check in from home?
A: Yes, if your organization allows remote work. Select "Remote Work" workplace and "Home Office" workstation.
Q: What if I forget to check out?
A: Contact your Team Lead or HR. They can manually close your attendance record.
Q: Can I change my shift after checking in?
A: No, shift is locked once you check in. You must check out first to change shifts.
Q: Why is my workstation dropdown empty?
A: You must select a workplace first. Workstations are filtered by selected workplace.
Q: What time should I check in?
A: Check in before your shift start time (e.g., 08:00 AM for Morning Shift).
Q: Can I check in early?
A: Yes, but you'll be marked as "Early Arrival" in the system.
DashboardController
├── ShiftServices (get user shifts)
├── AttendanceRecordServices (get attendance data)
└── view('users.employee.dashboard')
├── x-dashboard.employee.base (Layout Component)
│ └── dashboard.blade.php (Main View)
│ ├── Alpine.js (gridTimeCalendar)
│ ├── Shift Selector (Dropdown)
│ ├── Workplace Selector (Dropdown)
│ ├── Workstation Selector (Dropdown)
│ ├── Check In/Out Button
│ └── Attendance Calendar
└── Modal Components
└── toggle_modal (Shift Assignment Modal)
User Visits Dashboard
↓
DashboardController@dashboard
├─ ShiftServices->get() → Get all shifts
├─ UserShift Query → Get user's selected shift
└─ AttendanceRecordServices->getShiftAttendances()
↓
View Data Passed to Blade
├─ shifts (array)
├─ attendanceRecords (array)
└─ currentShift (object)
↓
Blade Template Rendered
└─ Alpine.js Initializes
├─ loadUserShifts() → Load shift data
├─ loadAttendances() → Load attendance calendar
├─ loadCurrentAttance() → Load current status
└─ Ready for user interaction
File: app/Http/Controllers/Web/Employee/DashboardController.php
dashboard()public function dashboard(
ShiftServices $shiftServices,
AttendanceRecordServices $attendanceRecordServices
) {
// Get all available shifts
$shifts = $shiftServices->get();
// Get user's currently selected shift
$currentShift = UserShift::where('user_id', Auth::user()->id)
->where('is_selected', true)
->first();
// Get attendance records for current shift
$attendanceRecords = $attendanceRecordServices
->getShiftAttendances($currentShift->shift->id);
return view('users.employee.dashboard', compact([
'shifts',
'attendanceRecords'
]));
}
Responsibilities:
getWorkPlaces() (API)public function getWorkPlaces()
{
$workPlaces = WorkPlace::with(['workStations'])
->whereHas('employees', function ($q) {
$q->whereHas('profile', function ($q) {
$q->where('user_id', Auth::user()->id);
});
})
->get();
return response([
'workPlaces' => $workPlaces
]);
}
Purpose: Fetch available workplaces with nested workstations for current user
class ShiftServices
{
public function get()
{
return Shift::where('is_active', true)->get();
}
}
class AttendanceRecordServices
{
public function getShiftAttendances($shiftId)
{
return AttendanceRecord::where('shift_id', $shiftId)
->where('user_id', Auth::user()->id)
->get();
}
}
File: resources/views/users/employee/dashboard.blade.php
<x-dashboard.employee.base>
<!-- Dashboard content -->
</x-dashboard.employee.base>
Base Layout Component: components/dashboard/employee/base.blade.php
<div x-data="gridTimeCalendar"
class="flex flex-col gap-2"
x-init="loadAttendances({{ json_encode($attendanceRecords) }})">
Alpine.js Component: gridTimeCalendar
Initialization:
<div class="flex flex-col sm:flex-row sm:justify-between">
<!-- Shift Selector -->
<!-- Workplace Selector -->
<!-- Workstation Selector -->
<!-- Check In/Out Button -->
</div>
<template x-if="!currentAttendance?.status">
<select @change="changeShiftAction($event)"
class="select select-accent select-sm"
x-init="loadUserShifts({{ json_encode($userShifts) }})">
<template x-for="userShift in userShifts" :key="userShift.id">
<option :value="JSON.stringify(userShift)">
<span x-text="userShift.shift.name"></span> |
<span x-text="`${userShift.shift.time_started} -
${userShift.shift.time_ended}`"></span>
</option>
</template>
</select>
</template>
<template x-if="currentAttendance?.status == 'current'">
<select disabled class="select select-accent select-sm">
<!-- Same structure but disabled -->
</select>
</template>
Behavior:
loadUserShifts()<!-- Workplace Selector -->
<template x-if="!currentAttendance?.work_place">
<select @change="pickWorkPlace($event)"
class="select select-accent select-sm">
<option>Select Work Places</option>
<template x-for="workPlace in workPlaces" :key="workPlace.id">
<option :value="workPlace.id">
<span x-text="workPlace.name"></span>
</option>
</template>
</select>
</template>
<!-- Workstation Selector (conditional) -->
<template x-if="selectedWorkPlace">
<select @change="pickWorkStation($event)"
class="select select-accent select-sm">
<option>Select Work Stations</option>
<template x-for="workStation in selectedWorkPlace.work_stations"
:key="workStation.id">
<option :value="workStation.id">
<span x-text="workStation.name"></span>
</option>
</template>
</select>
</template>
<template x-if="!currentAttendance?.status">
<button @click="confirmCheckIn"
:class="`text-white btn btn-accent btn-md ` +
(isLoading ? 'btn-disabled' : '')">
Check In
<span x-show="isLoading" class="loading loading-spinner">
</span>
</button>
</template>
<template x-if="currentAttendance?.status == 'current'">
<button @click="confirmCheckOut"
:class="`btn btn-error btn-sm ` +
(isLoading ? 'btn-disabled' : '')">
<span>Check Out</span>
<span x-show="isLoading" class="loading loading-spinner">
</span>
</button>
</template>
x-data="gridTimeCalendar"
State Variables:
{
currentAttendance: null, // Current check-in record
userShifts: [], // User's available shifts
workPlaces: [], // Available workplaces
selectedWorkPlace: null, // Selected workplace object
isLoading: false, // Loading state
errors: {}, // Validation errors
attendanceRecords: [] // Calendar data
}
loadAttendances(data)loadAttendances(attendanceRecords) {
this.attendanceRecords = attendanceRecords;
// Render calendar grid
this.renderCalendar();
}
loadCurrentAttance(records)loadCurrentAttance(records) {
this.currentAttendance = records
.find(r => r.status === 'current');
}
loadUserShifts(shifts)loadUserShifts(shifts) {
this.userShifts = shifts;
}
changeShiftAction(event)changeShiftAction(event) {
const shift = JSON.parse(event.target.value);
// Update selected shift
// Dispatch to server
}
pickWorkPlace(event)pickWorkPlace(event) {
const workPlaceId = event.target.value;
this.selectedWorkPlace = this.workPlaces
.find(wp => wp.id == workPlaceId);
}
confirmCheckIn()confirmCheckIn() {
// Validate selections
if (!this.validateCheckIn()) return;
this.isLoading = true;
// Send to server
fetch('/api/attendance/check-in', {
method: 'POST',
body: JSON.stringify({
shift_id: this.selectedShift.id,
work_place_id: this.selectedWorkPlace.id,
work_station_id: this.selectedWorkStation.id,
})
})
.then(res => res.json())
.then(data => {
this.currentAttendance = data;
this.isLoading = false;
});
}
confirmCheckOut()confirmCheckOut() {
this.isLoading = true;
fetch('/api/attendance/check-out', {
method: 'POST'
})
.then(res => res.json())
.then(data => {
this.currentAttendance = null;
this.isLoading = false;
// Reset selectors
});
}
// In routes/web.php
Route::middleware('auth')->group(function () {
Route::get('/employee/dashboard',
[DashboardController::class, 'dashboard'])
->name('employee.dashboard');
Route::get('/api/workplaces',
[DashboardController::class, 'getWorkPlaces'])
->name('api.workplaces');
});
class UserShift extends Model
{
public function user() {
return $this->belongsTo(User::class);
}
public function shift() {
return $this->belongsTo(Shift::class);
}
}
class AttendanceRecord extends Model
{
public function user() {
return $this->belongsTo(User::class);
}
public function shift() {
return $this->belongsTo(Shift::class);
}
public function workplace() {
return $this->belongsTo(WorkPlace::class);
}
public function workstation() {
return $this->belongsTo(WorkStation::class);
}
}
{
"id": 1,
"name": "Morning",
"time_started": "08:00",
"time_ended": "17:00",
"is_active": true
}
{
"id": 1,
"name": "Main Office - 3rd Floor",
"work_stations": [
{
"id": 1,
"name": "Desk 5-A"
},
{
"id": 2,
"name": "Desk 5-B"
}
]
}
{
"id": 1,
"user_id": 5,
"shift_id": 1,
"work_place_id": 1,
"work_station_id": 1,
"check_in": "2025-12-04 08:30:15",
"check_out": "2025-12-04 17:15:00",
"status": "current" | "approved" | "pending",
"duration_minutes": 525
}
| Component | Responsibility |
|---|---|
| Controller | Load data, validate requests |
| Service | Business logic, data retrieval |
| Blade | Render HTML, Alpine.js setup |
| Alpine.js | Interactive UI, state management |
| API | Handle async requests |
File Structure:
app/Http/Controllers/Web/Employee/
└── DashboardController.php
resources/views/users/employee/
└── dashboard.blade.php
resources/components/dashboard/employee/
└── base.blade.php
Documentation Version: 1.0
Last Updated: December 4, 2025
Status: ✅ Complete