Files
Odoo-Modules/fusion_clock/views/portal_timesheet_templates.xml
gsinghpal a479052b72 fix(fusion_clock): portal white-border + responsive timesheet entries
- White border on every portal page: the .fclk-app full-bleed relied on exact
  negative margins to cancel the portal layout's container padding; when it
  didn't match, the white page chrome showed through. Match the PAGE background
  to the app (light #f3f4f6 / dark #0f1117, via body:has(.fclk-app)) so the
  gutter is invisible, and clip horizontal overflow.
- Timesheets not responsive: the 6-column table crammed/wrapped on phones.
  Replaced the table with stacked cards (date + net up top, in -> out, then
  break / location / Correct) that read cleanly at any width. Correction-link
  data attributes preserved; the xpath-inherited .fclk-nav-bar untouched.

Live on entech 19.0.3.12.2 (both rules verified in the served frontend bundle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:20:56 -04:00

157 lines
9.0 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Timesheet Portal Page -->
<template id="portal_timesheet_page" name="Fusion Clock Timesheets">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="False"/>
<t t-set="no_breadcrumbs" t-value="True"/>
<t t-set="no_header" t-value="True"/>
<div class="fclk-app">
<div class="fclk-timesheet-container">
<!-- Header -->
<div class="fclk-ts-header">
<h2>Timesheets</h2>
<div class="fclk-ts-period-nav">
<a t-attf-href="/my/clock/timesheets?period=last"
t-attf-class="fclk-ts-period-btn {{ 'fclk-ts-period-btn-active' if period == 'last' else '' }}">
Previous
</a>
<a t-attf-href="/my/clock/timesheets?period=current"
t-attf-class="fclk-ts-period-btn {{ 'fclk-ts-period-btn-active' if period == 'current' else '' }}">
Current
</a>
</div>
</div>
<!-- Period Info -->
<div style="margin-bottom:16px;">
<span class="fclk-ts-period-btn" style="cursor:default; display:inline-block;">
<t t-esc="period_start.strftime('%b %d')"/> -
<t t-esc="period_end.strftime('%b %d, %Y')"/>
(<t t-esc="dict([('weekly','Weekly'),('biweekly','Bi-Weekly'),('semi_monthly','Semi-Monthly'),('monthly','Monthly')]).get(schedule_type, schedule_type)"/>)
</span>
</div>
<!-- Summary Cards -->
<div class="fclk-ts-summary">
<div class="fclk-ts-summary-card">
<div class="fclk-ts-summary-value"><t t-esc="total_hours"/>h</div>
<div class="fclk-ts-summary-label">Total Hours</div>
</div>
<div class="fclk-ts-summary-card">
<div class="fclk-ts-summary-value" style="color:#10B981;"><t t-esc="net_hours"/>h</div>
<div class="fclk-ts-summary-label">Net Hours</div>
</div>
<div class="fclk-ts-summary-card">
<div class="fclk-ts-summary-value"><t t-esc="int(total_breaks)"/>m</div>
<div class="fclk-ts-summary-label">Total Breaks</div>
</div>
</div>
<!-- Attendance Entries (responsive cards) -->
<t t-if="attendances">
<div class="fclk-ts-list">
<t t-foreach="attendances" t-as="entry">
<div class="fclk-ts-card">
<div class="fclk-ts-card-top">
<div class="fclk-ts-card-date">
<t t-esc="entry['day_name']"/>
<span><t t-esc="entry['day_date']"/></span>
</div>
<div class="fclk-ts-card-net">
<t t-esc="'%.1f' % (entry['att'].x_fclk_net_hours or 0)"/>h
</div>
</div>
<div class="fclk-ts-card-times">
<span><span class="fclk-ts-k">In</span><t t-esc="entry['time_in']"/></span>
<span class="fclk-ts-arrow">&#8594;</span>
<span>
<span class="fclk-ts-k">Out</span>
<t t-if="entry['att'].check_out">
<t t-esc="entry['time_out']"/>
<t t-if="entry['att'].x_fclk_auto_clocked_out">
<span class="fclk-ts-badge-auto">AUTO</span>
</t>
</t>
<t t-else="">
<span style="color:#f59e0b;">Active</span>
</t>
</span>
</div>
<div class="fclk-ts-card-meta">
<span><t t-esc="int(entry['att'].x_fclk_break_minutes or 0)"/>m break</span>
<t t-if="entry['att'].x_fclk_location_id">
<span class="fclk-ts-dot">&#183;</span>
<span><t t-esc="entry['att'].x_fclk_location_id.name or ''"/></span>
</t>
<a href="#" class="fclk-correction-link fclk-ts-correct"
t-att-data-att-id="entry['att'].id"
t-att-data-check-in="entry['att'].check_in.strftime('%Y-%m-%d %H:%M:%S') if entry['att'].check_in else ''"
t-att-data-check-out="entry['att'].check_out.strftime('%Y-%m-%d %H:%M:%S') if entry['att'].check_out else ''">
Correct
</a>
</div>
</div>
</t>
</div>
</t>
<t t-else="">
<div class="fclk-empty-state">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#6b7280" stroke-width="1.5">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>
<p>No attendance records for this period.</p>
</div>
</t>
<!-- Navigation Bar -->
<div class="fclk-nav-bar">
<a href="/my/clock" class="fclk-nav-item">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span>Clock</span>
</a>
<a href="/my/clock/timesheets" class="fclk-nav-item fclk-nav-active">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>
<span>Timesheets</span>
</a>
<a href="/my/clock/reports" class="fclk-nav-item">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
</svg>
<span>Reports</span>
</a>
<t t-if="show_payslips">
<a href="/my/clock/payslips" class="fclk-nav-item">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="5" width="20" height="14" rx="2"/>
<line x1="2" y1="10" x2="22" y2="10"/>
</svg>
<span>Payslips</span>
</a>
</t>
</div>
</div>
</div>
</t>
</template>
</odoo>