Model: fclk_create_open_shifts/claim_open_shift/release_shift (days-before cutoff + role eligibility)/bulk_apply. Planner: Open Shift… panel, open-shifts strip with delete, Apply-to-dept; load includes open shifts. Portal: claim open shifts + release own upcoming shifts with feedback banners. Tests for claim/role-gate/release/bulk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
216 lines
13 KiB
XML
216 lines
13 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
|
|
<template id="portal_schedule_page" name="Fusion Clock My Schedule">
|
|
<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-container">
|
|
|
|
<!-- Header -->
|
|
<div class="fclk-header">
|
|
<div class="fclk-date">My Schedule</div>
|
|
<h1 class="fclk-greeting">Hello, <t t-esc="employee.name.split(' ')[0]"/></h1>
|
|
</div>
|
|
|
|
<!-- Claim / release feedback -->
|
|
<div class="fpl-flash fpl-flash-err" t-if="error">
|
|
<t t-esc="error"/>
|
|
</div>
|
|
<div class="fpl-flash fpl-flash-ok" t-if="success">
|
|
<t t-if="success == 'claimed'">Shift claimed — it's now on your schedule.</t>
|
|
<t t-elif="success == 'released'">Shift released back to the open pool.</t>
|
|
<t t-else="">Done.</t>
|
|
</div>
|
|
|
|
<!-- Open shifts available to claim -->
|
|
<t t-if="open_shifts">
|
|
<div class="fpl-group">
|
|
<div class="fpl-group-title">Open Shifts — Available to Claim</div>
|
|
<div class="fpl-list">
|
|
<t t-foreach="open_shifts" t-as="op">
|
|
<div class="fclk-recent-item fpl-open-item">
|
|
<div class="fclk-recent-info">
|
|
<div class="fclk-recent-location">
|
|
<t t-esc="op['date_full']"/>
|
|
<t t-if="op['role_name']"> · <t t-esc="op['role_name']"/></t>
|
|
</div>
|
|
<div class="fclk-recent-times"><t t-esc="op['time_range']"/></div>
|
|
</div>
|
|
<form method="post" action="/my/clock/schedule/claim" class="fpl-claim-form">
|
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
|
<input type="hidden" name="schedule_id" t-att-value="op['id']"/>
|
|
<button type="submit" class="btn btn-sm btn-success">Claim</button>
|
|
</form>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
|
|
<!-- Next Shift Card (if any upcoming) -->
|
|
<t t-if="next_slot">
|
|
<div class="fclk-status-card fpl-next-shift">
|
|
<div class="fpl-next-label">Next Shift</div>
|
|
<div class="fpl-next-date"><t t-esc="next_slot['date']"/></div>
|
|
<div class="fpl-next-time"><t t-esc="next_slot['time']"/></div>
|
|
<div class="fpl-next-role" t-if="next_slot['role']">
|
|
<t t-esc="next_slot['role']"/>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
<t t-else="">
|
|
<div class="fclk-status-card fpl-empty-card">
|
|
<div class="fpl-empty-icon">
|
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#10B981" 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>
|
|
</div>
|
|
<div class="fpl-empty-title">No upcoming shifts</div>
|
|
<div class="fpl-empty-sub">Your manager hasn't published any shifts yet.</div>
|
|
</div>
|
|
</t>
|
|
|
|
<!-- Summary Row -->
|
|
<div class="fclk-stats-row" t-if="slot_count">
|
|
<div class="fclk-stat-card">
|
|
<div class="fclk-stat-header">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10B981" 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"/>
|
|
</svg>
|
|
<span>Upcoming</span>
|
|
</div>
|
|
<div class="fclk-stat-value">
|
|
<t t-esc="slot_count"/>
|
|
</div>
|
|
<div class="fclk-stat-target">shifts scheduled</div>
|
|
</div>
|
|
<div class="fclk-stat-card">
|
|
<div class="fclk-stat-header">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<polyline points="12 6 12 12 16 14"/>
|
|
</svg>
|
|
<span>Total Hours</span>
|
|
</div>
|
|
<div class="fclk-stat-value">
|
|
<t t-esc="'%.1f' % sum(s['duration_hours'] for items in groups.values() for s in items)"/>h
|
|
</div>
|
|
<div class="fclk-stat-target">across upcoming</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Grouped Schedule List -->
|
|
<t t-foreach="groups.items()" t-as="group">
|
|
<div class="fpl-group">
|
|
<div class="fpl-group-title">
|
|
<t t-esc="group[0]"/>
|
|
</div>
|
|
<div class="fpl-list">
|
|
<t t-foreach="group[1]" t-as="item">
|
|
<div class="fclk-recent-item fpl-shift-item">
|
|
<div class="fclk-recent-date">
|
|
<div class="fclk-recent-day-name">
|
|
<t t-esc="item['day_label']"/>
|
|
</div>
|
|
<div class="fclk-recent-day-num">
|
|
<t t-esc="item['day_num']"/>
|
|
</div>
|
|
</div>
|
|
<div class="fclk-recent-info">
|
|
<div class="fclk-recent-location">
|
|
<t t-if="item['role_name']">
|
|
<t t-esc="item['role_name']"/>
|
|
</t>
|
|
<t t-else="">
|
|
Shift
|
|
</t>
|
|
</div>
|
|
<div class="fclk-recent-times">
|
|
<t t-esc="item['time_range']"/>
|
|
</div>
|
|
<div class="fpl-shift-note" t-if="item['note']">
|
|
<t t-esc="item['note']"/>
|
|
</div>
|
|
</div>
|
|
<div class="fclk-recent-hours">
|
|
<t t-esc="'%.1f' % item['duration_hours']"/>h
|
|
<form t-if="item.get('releasable')" method="post"
|
|
action="/my/clock/schedule/release" class="fpl-release-form">
|
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
|
<input type="hidden" name="schedule_id" t-att-value="item['schedule_id']"/>
|
|
<button type="submit" class="fpl-release-btn" title="Release this shift">Release</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</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">
|
|
<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/schedule" 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"/>
|
|
<line x1="8" y1="14" x2="10" y2="14"/>
|
|
<line x1="14" y1="14" x2="16" y2="14"/>
|
|
<line x1="8" y1="18" x2="10" y2="18"/>
|
|
<line x1="14" y1="18" x2="16" y2="18"/>
|
|
</svg>
|
|
<span>Schedule</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>
|