- contracts.js: built-in install chain fallback when no Flow Template matches on_contract_signed — every accepted contract now creates a master Issue + chained Dispatch Jobs (fiber_install template) so we never lose a signed contract to a missing flow config. - acceptance.js: export createDeferredJobs + propagate assigned_group into Dispatch Job payload (was only in notes, not queryable). - dispatch.js: chain-walk helpers (unblockDependents, _isChainTerminal, setJobStatusWithChain) + terminal-node detection that activates pending Service Subscriptions (En attente → Actif, start_date=tomorrow) and emits a prorated Sales Invoice covering tomorrow → EOM. Courtesy-day billing convention: activation day is free, first period starts next day. - dispatch.js: fix Sales Invoice 417 by resolving company default income account (Ventes - T) and passing company + income_account on each item. - dispatch.js: GET /dispatch/group-jobs + POST /dispatch/claim-job for tech self-assignment from the group queue; enriches with customer_name / service_location via per-job fetches since those fetch_from fields aren't queryable in list API. - TechTasksPage.vue: redesigned mobile-first UI with progress arc, status chips, and new "Tâches du groupe" section showing claimable unassigned jobs with a "Prendre" CTA. Live updates via SSE job-claimed / job-unblocked. - NetworkPage.vue + poller-control.js: poller toggle semantics flipped — green when enabled, red/gray when paused; explicit status chips for clarity. E2E verified end-to-end: CTR-00007 → 4 chained jobs → claim → In Progress → Completed walks chain → SUB-0000100002 activated (start=2026-04-24) → SINV-2026-700012 prorata $9.32 (= 39.95 × 7/30). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
364 lines
14 KiB
SCSS
364 lines
14 KiB
SCSS
// TARGO Tech Module — Mobile-first design tokens + base components
|
|
//
|
|
// The tech surface (`/j/*` routes) targets glove-on-screen readability,
|
|
// one-thumb reach, and TARGO brand identity. We deliberately don't reuse
|
|
// Quasar's default component chrome here — custom CSS gives us the pill
|
|
// radii, shadow layering, and motion curves trending design expects.
|
|
|
|
// ── 1. Tokens ────────────────────────────────────────────────────────────────
|
|
// Scoped to `.tech-surface` so the rest of the ops app is unaffected.
|
|
.tech-surface {
|
|
// TARGO palette — derived from existing #3f3d7a → #5c59a8 gradient
|
|
--tg-900: #2a2858; // deep night, pressed/active
|
|
--tg-800: #363566;
|
|
--tg-700: #3f3d7a; // current header start
|
|
--tg-500: #5c59a8; // current header end
|
|
--tg-300: #8b88d4; // tints, subtle accents
|
|
--tg-100: #dcdaf0; // avatar backgrounds
|
|
--tg-50: #f2f1fb; // wash backgrounds
|
|
|
|
// Gradients
|
|
--tg-hero: linear-gradient(135deg, #3f3d7a 0%, #5c59a8 100%);
|
|
--tg-hero-deep: linear-gradient(160deg, #2a2858 0%, #3f3d7a 60%, #5c59a8 100%);
|
|
|
|
// Signal — tuned warmer than Quasar defaults, better color-blind legibility
|
|
--sig-go: #10b981; // Completed / success
|
|
--sig-work: #f59e0b; // In Progress
|
|
--sig-stop: #ef4444; // Urgent / error
|
|
--sig-wait: #94a3b8; // On Hold (chain-blocked, invisible to tech)
|
|
--sig-info: #3b82f6; // Scheduled, neutral
|
|
|
|
// Surface + ink (warmer than current #eef1f5)
|
|
--surface: #fafbfd;
|
|
--surface-elev: #ffffff;
|
|
--surface-wash: #f2f1fb; // same as --tg-50
|
|
--surface-over: rgba(255,255,255,0.88); // for floating islands with backdrop blur
|
|
|
|
--ink-900: #0f172a;
|
|
--ink-700: #334155;
|
|
--ink-500: #64748b;
|
|
--ink-400: #94a3b8;
|
|
--ink-300: #cbd5e1;
|
|
|
|
// Elevation system (two-layer lift, not a single fat drop shadow)
|
|
--elev-1: 0 1px 2px rgba(15,23,42,0.04), 0 2px 6px rgba(15,23,42,0.03);
|
|
--elev-2: 0 2px 8px rgba(15,23,42,0.06), 0 12px 32px rgba(15,23,42,0.04);
|
|
--elev-3: 0 4px 14px rgba(15,23,42,0.08), 0 24px 48px rgba(15,23,42,0.06);
|
|
--elev-fab: 0 8px 24px rgba(16,185,129,0.35);
|
|
--elev-fab-purple: 0 8px 24px rgba(92,89,168,0.35);
|
|
|
|
// Radii
|
|
--r-sm: 10px;
|
|
--r-md: 16px;
|
|
--r-lg: 20px;
|
|
--r-xl: 28px;
|
|
--r-pill: 999px;
|
|
|
|
// Motion — Apple spring for page-level, shorter spring for micro
|
|
--ease-spring: cubic-bezier(0.32, 0.72, 0, 1);
|
|
--ease-micro: cubic-bezier(0.4, 0, 0.2, 1);
|
|
--dur-fast: 160ms;
|
|
--dur-mid: 240ms;
|
|
--dur-slow: 360ms;
|
|
|
|
// Type scale
|
|
--t-display: 600 2rem/1.1 system-ui, -apple-system, sans-serif; // 32px hero
|
|
--t-h1: 700 1.5rem/1.2 system-ui, -apple-system, sans-serif; // 24px
|
|
--t-h2: 700 1.25rem/1.25 system-ui, -apple-system, sans-serif; // 20px
|
|
--t-body: 500 1rem/1.4 system-ui, -apple-system, sans-serif; // 16px
|
|
--t-body-sm: 500 0.9375rem/1.4 system-ui, -apple-system, sans-serif; // 15px
|
|
--t-caption: 500 0.8125rem/1.3 system-ui, -apple-system, sans-serif; // 13px
|
|
--t-micro: 600 0.6875rem/1.2 system-ui, -apple-system, sans-serif; // 11px
|
|
|
|
// Base surface
|
|
background: var(--surface);
|
|
color: var(--ink-900);
|
|
min-height: 100vh;
|
|
font-feature-settings: 'cv11', 'ss01'; // nicer tabular digits
|
|
}
|
|
|
|
// Every tech page needs room below its content for the floating tab island
|
|
// (96px pill + 16px margin + safe-area). We do this with a utility class
|
|
// `.tg-page` so pages opt in explicitly, and for the legacy Quasar
|
|
// `<q-page padding>` pages we extend that element with extra padding too.
|
|
.tech-surface.q-page {
|
|
padding-bottom: calc(96px + env(safe-area-inset-bottom)) !important;
|
|
}
|
|
|
|
// ── 2. Typography utilities ─────────────────────────────────────────────────
|
|
.tech-surface {
|
|
.t-display { font: var(--t-display); letter-spacing: -0.02em; }
|
|
.t-h1 { font: var(--t-h1); letter-spacing: -0.01em; }
|
|
.t-h2 { font: var(--t-h2); letter-spacing: -0.005em; }
|
|
.t-body { font: var(--t-body); }
|
|
.t-body-sm { font: var(--t-body-sm); }
|
|
.t-caption { font: var(--t-caption); color: var(--ink-500); }
|
|
.t-micro { font: var(--t-micro); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-500); }
|
|
|
|
.t-ink-900 { color: var(--ink-900); }
|
|
.t-ink-700 { color: var(--ink-700); }
|
|
.t-ink-500 { color: var(--ink-500); }
|
|
.t-ink-400 { color: var(--ink-400); }
|
|
.t-ink-on-hero { color: rgba(255,255,255,0.95); }
|
|
.t-ink-on-hero-dim { color: rgba(255,255,255,0.7); }
|
|
}
|
|
|
|
// ── 3. Card — elevated pill-radius, no borders ──────────────────────────────
|
|
.tech-surface {
|
|
.tg-card {
|
|
background: var(--surface-elev);
|
|
border-radius: var(--r-lg);
|
|
box-shadow: var(--elev-2);
|
|
padding: 18px;
|
|
transition: transform var(--dur-fast) var(--ease-micro),
|
|
box-shadow var(--dur-fast) var(--ease-micro);
|
|
|
|
&.tg-card--wash { background: var(--surface-wash); box-shadow: none; }
|
|
&.tg-card--flat { box-shadow: var(--elev-1); }
|
|
&.tg-card--tap {
|
|
cursor: pointer;
|
|
-webkit-tap-highlight-color: transparent;
|
|
&:active { transform: scale(0.985); box-shadow: var(--elev-1); }
|
|
}
|
|
&.tg-card--done { opacity: 0.62; }
|
|
}
|
|
}
|
|
|
|
// ── 4. Status chip — pill badge (replaces border-left indicator) ────────────
|
|
.tech-surface {
|
|
.tg-chip {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
padding: 4px 10px;
|
|
border-radius: var(--r-pill);
|
|
font: var(--t-micro);
|
|
white-space: nowrap;
|
|
|
|
.tg-chip-dot {
|
|
width: 6px; height: 6px; border-radius: 50%;
|
|
display: inline-block;
|
|
}
|
|
|
|
&.is-go { background: rgba(16,185,129,0.12); color: #047857; .tg-chip-dot { background: var(--sig-go); } }
|
|
&.is-work { background: rgba(245,158,11,0.14); color: #b45309; .tg-chip-dot { background: var(--sig-work); } }
|
|
&.is-stop { background: rgba(239,68,68,0.12); color: #b91c1c; .tg-chip-dot { background: var(--sig-stop); } }
|
|
&.is-wait { background: rgba(148,163,184,0.16); color: #475569; .tg-chip-dot { background: var(--sig-wait); } }
|
|
&.is-info { background: rgba(59,130,246,0.12); color: #1d4ed8; .tg-chip-dot { background: var(--sig-info); } }
|
|
&.is-tg { background: rgba(92,89,168,0.12); color: var(--tg-700); .tg-chip-dot { background: var(--tg-500); } }
|
|
}
|
|
}
|
|
|
|
// ── 5. Button system — pill, FAB, ghost ─────────────────────────────────────
|
|
.tech-surface {
|
|
.tg-btn {
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
gap: 8px;
|
|
min-height: 52px;
|
|
padding: 0 24px;
|
|
border: 0;
|
|
border-radius: var(--r-pill);
|
|
font: 700 1rem/1 system-ui, -apple-system, sans-serif;
|
|
letter-spacing: 0.01em;
|
|
cursor: pointer;
|
|
transition: transform var(--dur-fast) var(--ease-micro),
|
|
box-shadow var(--dur-fast) var(--ease-micro),
|
|
background var(--dur-fast) var(--ease-micro);
|
|
-webkit-tap-highlight-color: transparent;
|
|
user-select: none;
|
|
|
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
&:active:not(:disabled) { transform: scale(0.97); }
|
|
|
|
&.is-block { width: 100%; }
|
|
|
|
// Variants
|
|
&.is-primary {
|
|
background: var(--tg-700); color: #fff;
|
|
box-shadow: var(--elev-fab-purple);
|
|
&:active { background: var(--tg-900); }
|
|
}
|
|
&.is-success {
|
|
background: var(--sig-go); color: #fff;
|
|
box-shadow: var(--elev-fab);
|
|
&:active { background: #059669; }
|
|
}
|
|
&.is-warning {
|
|
background: var(--sig-work); color: #fff;
|
|
box-shadow: 0 8px 24px rgba(245,158,11,0.35);
|
|
}
|
|
&.is-ghost {
|
|
background: var(--surface-wash); color: var(--tg-700);
|
|
box-shadow: none;
|
|
&:active { background: var(--tg-100); }
|
|
}
|
|
&.is-outline {
|
|
background: transparent; color: var(--tg-700);
|
|
box-shadow: inset 0 0 0 1.5px var(--tg-300);
|
|
}
|
|
&.is-text {
|
|
background: transparent; color: var(--tg-700);
|
|
box-shadow: none; min-height: 40px; padding: 0 12px;
|
|
}
|
|
|
|
&.is-compact { min-height: 40px; padding: 0 16px; font-size: 0.9375rem; }
|
|
}
|
|
|
|
// Sticky floating CTA pill — sits above the tab bar
|
|
.tg-fab-cta {
|
|
position: fixed;
|
|
left: 16px; right: 16px;
|
|
bottom: calc(var(--tab-island-bottom, 88px) + env(safe-area-inset-bottom));
|
|
z-index: 20;
|
|
}
|
|
}
|
|
|
|
// ── 6. Hero header — big gradient banner ────────────────────────────────────
|
|
.tech-surface {
|
|
.tg-hero {
|
|
background: var(--tg-hero-deep);
|
|
color: #fff;
|
|
padding: 18px 18px 28px;
|
|
border-radius: 0 0 var(--r-xl) var(--r-xl);
|
|
position: relative;
|
|
overflow: hidden;
|
|
|
|
// Subtle radial highlight for depth
|
|
&::before {
|
|
content: '';
|
|
position: absolute; inset: 0;
|
|
background: radial-gradient(1200px 400px at 85% -20%, rgba(255,255,255,0.14), transparent 60%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tg-hero-inner { position: relative; z-index: 1; }
|
|
}
|
|
}
|
|
|
|
// ── 7. Floating tab island (bottom nav) ─────────────────────────────────────
|
|
.tech-surface .tg-tab-island,
|
|
.tg-tab-island {
|
|
position: fixed;
|
|
left: 16px; right: 16px;
|
|
bottom: calc(16px + env(safe-area-inset-bottom));
|
|
height: 64px;
|
|
border-radius: var(--r-xl);
|
|
background: var(--surface-over);
|
|
backdrop-filter: saturate(180%) blur(22px);
|
|
-webkit-backdrop-filter: saturate(180%) blur(22px);
|
|
box-shadow: var(--elev-3);
|
|
border: 1px solid rgba(15,23,42,0.04);
|
|
display: flex; align-items: center; justify-content: space-around;
|
|
z-index: 30;
|
|
padding: 0 8px;
|
|
|
|
.tg-tab {
|
|
flex: 1; display: flex; flex-direction: column; align-items: center;
|
|
justify-content: center; gap: 2px;
|
|
height: 56px; border-radius: var(--r-md);
|
|
cursor: pointer; color: var(--ink-500);
|
|
transition: color var(--dur-fast) var(--ease-micro);
|
|
-webkit-tap-highlight-color: transparent;
|
|
text-decoration: none;
|
|
|
|
.tg-tab-icon { font-size: 24px; line-height: 1; }
|
|
.tg-tab-label { font: var(--t-micro); }
|
|
|
|
&.is-active {
|
|
color: var(--tg-700);
|
|
.tg-tab-icon { transform: translateY(-1px); }
|
|
}
|
|
|
|
&:active { background: var(--tg-50); }
|
|
}
|
|
}
|
|
|
|
// ── 8. Page spacing (reserve room for floating tab island) ──────────────────
|
|
// Bottom-room is handled at the q-page level (.tech-surface.q-page above),
|
|
// so inner content containers don't need to add their own bottom padding —
|
|
// they just flow naturally and the tab-island gap is guaranteed.
|
|
.tech-surface .tg-page { min-height: 100vh; }
|
|
|
|
// ── 9. Peek sheet (bottom card that slides up) ──────────────────────────────
|
|
.tech-surface {
|
|
.tg-peek-backdrop {
|
|
position: fixed; inset: 0; z-index: 40;
|
|
background: rgba(15,23,42,0.4);
|
|
backdrop-filter: blur(2px);
|
|
opacity: 0; pointer-events: none;
|
|
transition: opacity var(--dur-mid) var(--ease-micro);
|
|
&.is-open { opacity: 1; pointer-events: auto; }
|
|
}
|
|
|
|
.tg-peek {
|
|
position: fixed; left: 0; right: 0; bottom: 0;
|
|
z-index: 41;
|
|
background: var(--surface-elev);
|
|
border-radius: var(--r-xl) var(--r-xl) 0 0;
|
|
box-shadow: var(--elev-3);
|
|
padding: 8px 0 calc(16px + env(safe-area-inset-bottom));
|
|
transform: translateY(100%);
|
|
transition: transform var(--dur-mid) var(--ease-spring);
|
|
max-height: 82vh;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
|
|
&.is-open { transform: translateY(0); }
|
|
|
|
.tg-peek-handle {
|
|
width: 40px; height: 4px; border-radius: 2px;
|
|
background: var(--ink-300); margin: 6px auto 4px;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── 10. Progress arc (SVG ring for hero stat) ───────────────────────────────
|
|
.tech-surface {
|
|
.tg-arc {
|
|
position: relative;
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
|
|
svg { transform: rotate(-90deg); }
|
|
|
|
.tg-arc-track { stroke: rgba(255,255,255,0.16); fill: none; }
|
|
.tg-arc-fill { stroke: #fff; fill: none; stroke-linecap: round;
|
|
transition: stroke-dashoffset var(--dur-slow) var(--ease-spring); }
|
|
|
|
.tg-arc-center {
|
|
position: absolute; inset: 0;
|
|
display: flex; flex-direction: column;
|
|
align-items: center; justify-content: center;
|
|
text-align: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── 11. Offline / system banner ─────────────────────────────────────────────
|
|
.tech-surface .tg-banner {
|
|
position: fixed;
|
|
top: env(safe-area-inset-top); left: 0; right: 0;
|
|
z-index: 50;
|
|
padding: 6px 16px;
|
|
background: var(--sig-stop); color: #fff;
|
|
font: var(--t-caption);
|
|
text-align: center;
|
|
display: flex; align-items: center; justify-content: center; gap: 6px;
|
|
}
|
|
|
|
// ── 12. Avatar ──────────────────────────────────────────────────────────────
|
|
.tech-surface .tg-avatar {
|
|
width: 40px; height: 40px;
|
|
border-radius: 50%;
|
|
background: var(--tg-900);
|
|
color: #fff;
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
font: 700 0.875rem/1 system-ui;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
// ── 13. Route transitions (slide-up for card→detail) ────────────────────────
|
|
.tech-slide-enter-active, .tech-slide-leave-active {
|
|
transition: transform var(--dur-mid) var(--ease-spring),
|
|
opacity var(--dur-mid) var(--ease-micro);
|
|
}
|
|
.tech-slide-enter-from { transform: translateY(24px); opacity: 0; }
|
|
.tech-slide-leave-to { transform: translateY(24px); opacity: 0; }
|