feat(fusion_clock): redesign dashboard — layered, role-aware, gradient cards (dark+light)
- Rework /fusion_clock/dashboard_data into a personal block (everyone) plus a team block (team lead = direct reports, manager = org-wide). A regular employee's payload never contains another employee's data. - New OWL stacked layout: gradient KPI cards (Today/Week/OT/Streak), Today's Shift, Recent Activity, Upcoming Leave, Recent Penalties; team band adds Present/Absent/Late/Pending, roster, and Needs Attention. - Dark/light via compile-time $o-webclient-color-scheme branching; drop the old runtime html.o_dark dashboard block. - Open the Dashboard menu to group_fusion_clock_user (lead/manager imply). - Add HttpCase permission/no-leak tests. Bump 3.13.2 -> 3.14.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,148 +2,198 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="fusion_clock.Dashboard">
|
||||
<div class="o_action">
|
||||
<div class="container-fluid py-3">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="mb-0">Fusion Clock Dashboard</h2>
|
||||
<button class="btn btn-outline-primary" t-on-click="onRefresh">
|
||||
<i class="fa fa-refresh"/> Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_action fclk-dash">
|
||||
<div class="fclk-dash-wrap">
|
||||
|
||||
<t t-if="state.loading">
|
||||
<div class="text-center py-5">
|
||||
<div class="fclk-dash-empty">
|
||||
<i class="fa fa-spinner fa-spin fa-2x"/>
|
||||
<p class="mt-2">Loading dashboard...</p>
|
||||
<p class="mt-2">Loading dashboard…</p>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="state.error">
|
||||
<div class="alert alert-danger">
|
||||
<t t-esc="state.error"/>
|
||||
</div>
|
||||
<div class="fclk-dash-card"><t t-esc="state.error"/></div>
|
||||
</t>
|
||||
|
||||
<t t-if="!state.loading and !state.error">
|
||||
<!-- Summary Cards -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="fclk-dash-card fclk-dash-card--total">
|
||||
<div class="fclk-dash-card-icon">
|
||||
<i class="fa fa-users"/>
|
||||
</div>
|
||||
<div class="fclk-dash-card-value"><t t-esc="state.total_employees"/></div>
|
||||
<div class="fclk-dash-card-label">Total Employees</div>
|
||||
</div>
|
||||
<!-- HEADER -->
|
||||
<div class="fclk-dash-header">
|
||||
<div>
|
||||
<div class="fclk-dash-hello"><t t-esc="greeting"/>, <t t-esc="state.personal.employee_name"/> 👋</div>
|
||||
<div class="fclk-dash-date"><t t-esc="todayLabel"/></div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="fclk-dash-card fclk-dash-card--present">
|
||||
<div class="fclk-dash-card-icon">
|
||||
<i class="fa fa-check-circle"/>
|
||||
</div>
|
||||
<div class="fclk-dash-card-value"><t t-esc="state.present_count"/></div>
|
||||
<div class="fclk-dash-card-label">Present Today</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="fclk-dash-card fclk-dash-card--absent">
|
||||
<div class="fclk-dash-card-icon">
|
||||
<i class="fa fa-times-circle"/>
|
||||
</div>
|
||||
<div class="fclk-dash-card-value"><t t-esc="state.absent_count"/></div>
|
||||
<div class="fclk-dash-card-label">Absent Today</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="fclk-dash-card fclk-dash-card--late">
|
||||
<div class="fclk-dash-card-icon">
|
||||
<i class="fa fa-clock-o"/>
|
||||
</div>
|
||||
<div class="fclk-dash-card-value"><t t-esc="state.late_count"/></div>
|
||||
<div class="fclk-dash-card-label">Late Today</div>
|
||||
</div>
|
||||
<div class="fclk-dash-headctl">
|
||||
<span class="fclk-dash-statusbadge" t-att-class="{'is-out': !state.personal.is_checked_in}">
|
||||
<t t-if="state.personal.is_checked_in">● Clocked in</t>
|
||||
<t t-else="">○ Not clocked in</t>
|
||||
</span>
|
||||
<button class="fclk-dash-btn-primary" t-on-click="onOpenClock">Open My Clock</button>
|
||||
<button class="fclk-dash-btn-ghost" t-on-click="onRefresh"><i class="fa fa-refresh"/></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Currently Clocked In -->
|
||||
<div class="col-md-8 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Currently Clocked In</h5>
|
||||
<span class="badge bg-success"><t t-esc="state.clocked_in.length"/> active</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<t t-if="state.clocked_in.length === 0">
|
||||
<div class="text-center py-4 text-muted">
|
||||
No employees currently clocked in
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Employee</th>
|
||||
<th>Clock-In</th>
|
||||
<th>Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="state.clocked_in" t-as="emp" t-key="emp_index">
|
||||
<tr>
|
||||
<td><t t-esc="emp.employee"/></td>
|
||||
<td><t t-esc="emp.check_in"/></td>
|
||||
<td><t t-esc="emp.location"/></td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
<!-- PERSONAL KPIs -->
|
||||
<div class="fclk-kpi-row">
|
||||
<div class="fclk-kpi fclk-kpi--today">
|
||||
<div class="fclk-kpi-ic">⏱</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.personal.today_hours"/>h</div>
|
||||
<div class="fclk-kpi-lbl">Today</div>
|
||||
</div>
|
||||
<div class="fclk-kpi fclk-kpi--week">
|
||||
<div class="fclk-kpi-ic">📅</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.personal.week_hours"/>h</div>
|
||||
<div class="fclk-kpi-lbl">This Week</div>
|
||||
</div>
|
||||
<div class="fclk-kpi fclk-kpi--ot">
|
||||
<div class="fclk-kpi-ic">⚡</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.personal.overtime_week"/>h</div>
|
||||
<div class="fclk-kpi-lbl">OT This Week</div>
|
||||
</div>
|
||||
<div class="fclk-kpi fclk-kpi--streak">
|
||||
<div class="fclk-kpi-ic">🔥</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.personal.ontime_streak"/></div>
|
||||
<div class="fclk-kpi-lbl">On-time Streak</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PERSONAL DETAIL -->
|
||||
<div class="fclk-dash-2col">
|
||||
<div class="fclk-dash-card">
|
||||
<h4>Today's Shift</h4>
|
||||
<div class="fclk-dash-line">
|
||||
<span>Scheduled</span>
|
||||
<span class="fclk-dash-muted">
|
||||
<t t-if="state.personal.shift.label"><t t-esc="state.personal.shift.label"/> (<t t-esc="state.personal.shift.hours"/>h)</t>
|
||||
<t t-else="">Not scheduled today</t>
|
||||
</span>
|
||||
</div>
|
||||
<div class="fclk-dash-line">
|
||||
<span>Status</span>
|
||||
<span t-att-class="state.personal.is_checked_in ? 'fclk-pin' : 'fclk-dash-muted'"><t t-esc="state.personal.shift.status_note"/></span>
|
||||
</div>
|
||||
<div class="fclk-dash-line">
|
||||
<span>Source</span>
|
||||
<span class="fclk-dash-muted"><t t-esc="sourceLabel(state.personal.shift.source)"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fclk-dash-card">
|
||||
<h4>My Recent Activity</h4>
|
||||
<t t-if="state.personal.recent_activity.length === 0">
|
||||
<div class="fclk-dash-empty">No recent activity</div>
|
||||
</t>
|
||||
<t t-foreach="state.personal.recent_activity" t-as="a" t-key="a_index">
|
||||
<div class="fclk-dash-line">
|
||||
<span><t t-esc="fmtDate(a.check_in)"/></span>
|
||||
<span class="fclk-dash-muted">
|
||||
<t t-esc="a.worked_hours"/>h<t t-if="a.overtime_hours > 0"> · +<t t-esc="a.overtime_hours"/> OT</t>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fclk-dash-2col">
|
||||
<div class="fclk-dash-card">
|
||||
<h4>Upcoming Leave</h4>
|
||||
<t t-if="state.personal.leaves.length === 0">
|
||||
<div class="fclk-dash-empty">No upcoming leave</div>
|
||||
</t>
|
||||
<t t-foreach="state.personal.leaves" t-as="lv" t-key="lv_index">
|
||||
<div class="fclk-dash-line"><span><t t-esc="lv.label"/></span><span class="fclk-dash-muted"><t t-esc="lv.state"/></span></div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="fclk-dash-card">
|
||||
<h4>Recent Penalties</h4>
|
||||
<t t-if="state.personal.penalties.length === 0">
|
||||
<div class="fclk-dash-empty">None this month 🎉</div>
|
||||
</t>
|
||||
<t t-foreach="state.personal.penalties" t-as="p" t-key="p_index">
|
||||
<div class="fclk-dash-line"><span><t t-esc="p.type"/> · <t t-esc="p.date"/></span><span class="fclk-pyel">−<t t-esc="p.minutes"/> min</span></div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TEAM / ORG BAND -->
|
||||
<t t-if="state.team">
|
||||
<div class="fclk-dash-divider"><span>Team / Org</span></div>
|
||||
|
||||
<div class="fclk-kpi-row">
|
||||
<div class="fclk-kpi fclk-kpi--present">
|
||||
<div class="fclk-kpi-ic">✅</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.team.present_count"/></div>
|
||||
<div class="fclk-kpi-lbl">Present now</div>
|
||||
</div>
|
||||
<div class="fclk-kpi fclk-kpi--absent">
|
||||
<div class="fclk-kpi-ic">🚫</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.team.absent_count"/></div>
|
||||
<div class="fclk-kpi-lbl">Absent today</div>
|
||||
</div>
|
||||
<div class="fclk-kpi fclk-kpi--late">
|
||||
<div class="fclk-kpi-ic">⏰</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.team.late_count"/></div>
|
||||
<div class="fclk-kpi-lbl">Late today</div>
|
||||
</div>
|
||||
<div class="fclk-kpi fclk-kpi--pending">
|
||||
<div class="fclk-kpi-ic">📨</div>
|
||||
<div class="fclk-kpi-val"><t t-esc="state.team.pending_approvals"/></div>
|
||||
<div class="fclk-kpi-lbl">Pending approvals</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerts Panel -->
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Alerts</h5>
|
||||
<div class="fclk-dash-2col">
|
||||
<div class="fclk-dash-card">
|
||||
<h4>Currently Clocked In <span class="fclk-dash-muted"><t t-esc="state.team.present_count"/> of <t t-esc="state.team.total_employees"/></span></h4>
|
||||
<t t-if="state.team.clocked_in.length === 0">
|
||||
<div class="fclk-dash-empty">No one is clocked in right now</div>
|
||||
</t>
|
||||
<t t-foreach="state.team.clocked_in" t-as="emp" t-key="emp_index">
|
||||
<div class="fclk-dash-line">
|
||||
<span><span class="fclk-dash-av"><t t-esc="initials(emp.employee)"/></span><t t-esc="emp.employee"/>
|
||||
<span t-if="emp.late" class="fclk-dash-late-badge">late</span>
|
||||
</span>
|
||||
<span class="fclk-dash-muted"><t t-esc="fmtTime(emp.check_in)"/> · <t t-esc="emp.location"/></span>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="fclk-dash-card">
|
||||
<h4>Needs Attention</h4>
|
||||
<div class="fclk-dash-line" t-on-click="onViewActivityLogs" style="cursor:pointer;">
|
||||
<span class="fclk-pred"><t t-esc="state.team.absent_count"/> absent (no leave)</span>
|
||||
<span class="fclk-dash-muted">review →</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 cursor-pointer"
|
||||
t-on-click="onViewActivityLogs">
|
||||
<span><i class="fa fa-exclamation-circle text-warning me-2"/>Pending Reasons</span>
|
||||
<span class="badge bg-warning"><t t-esc="state.pending_reasons"/></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 cursor-pointer"
|
||||
t-on-click="onViewCorrections">
|
||||
<span><i class="fa fa-edit text-info me-2"/>Pending Corrections</span>
|
||||
<span class="badge bg-info"><t t-esc="state.pending_corrections"/></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center cursor-pointer"
|
||||
t-on-click="onViewPenalties">
|
||||
<span><i class="fa fa-clock-o text-danger me-2"/>Late Today</span>
|
||||
<span class="badge bg-danger"><t t-esc="state.late_count"/></span>
|
||||
</div>
|
||||
<div class="fclk-dash-line">
|
||||
<span><t t-esc="state.team.on_leave_count"/> on approved leave</span>
|
||||
<span class="fclk-dash-muted">today</span>
|
||||
</div>
|
||||
<div class="fclk-dash-line" t-on-click="onViewActivityLogs" style="cursor:pointer;">
|
||||
<span class="fclk-pyel"><t t-esc="state.team.pending_reasons"/> auto clock-out — reason pending</span>
|
||||
<span class="fclk-dash-muted">view →</span>
|
||||
</div>
|
||||
<div class="fclk-dash-line" t-on-click="onViewCorrections" style="cursor:pointer;">
|
||||
<span><t t-esc="state.team.pending_approvals"/> correction requests</span>
|
||||
<span class="fclk-dash-muted">open →</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Quick Actions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<button class="btn btn-outline-primary w-100 mb-2" t-on-click="onViewAttendances">
|
||||
<i class="fa fa-list me-1"/> View All Attendances
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary w-100" t-on-click="onViewActivityLogs">
|
||||
<i class="fa fa-history me-1"/> Activity Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- QUICK ACTIONS -->
|
||||
<div class="fclk-dash-card">
|
||||
<h4>Quick Actions</h4>
|
||||
<div class="fclk-dash-actions">
|
||||
<span class="fclk-dash-act" t-on-click="onOpenClock">🕒 Open My Clock</span>
|
||||
<span class="fclk-dash-act" t-on-click="onViewTimesheets">📄 My Timesheets</span>
|
||||
<t t-if="state.team">
|
||||
<span class="fclk-dash-act" t-on-click="onViewAttendances">📋 All Attendances</span>
|
||||
<span class="fclk-dash-act" t-on-click="onViewCorrections">📨 Approvals</span>
|
||||
<span class="fclk-dash-act" t-on-click="onViewPenalties">⚠ Penalties</span>
|
||||
<span class="fclk-dash-act" t-on-click="onViewActivityLogs">🗒 Activity Logs</span>
|
||||
</t>
|
||||
<t t-if="state.role === 'manager'">
|
||||
<span class="fclk-dash-act" t-on-click="onViewShiftPlanner">📅 Shift Planner</span>
|
||||
<span class="fclk-dash-act" t-on-click="onViewReports">📊 Reports</span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
Reference in New Issue
Block a user