Files
Webklar-Kundenbereich/public/kalkulation.css
Claude ac264dc9ba Rechnungsanalyse: Termin landet im Akquise-Ticket und damit im Planboard
Der im Gespraech vereinbarte Termin wird mit einem Klick am Akquise-Ticket
eingetragen (startDate/startTime/endTime/assignedTo/assignedName) - genau die
Felder, die das WOMS-Planboard liest. Kein zweiter Datensatz, damit Termin und
Ticket nicht auseinanderlaufen koennen.

- server/services/appointments.js: Ticket des Kunden finden, Termin schreiben,
  belegte Zeiten lesen; Umrechnung ISO <-> "dd.mm.yyyy" und "hh:mm" <-> "hhmm"
- Routen GET /api/portal-admin/appointments und
  POST /api/portal-admin/customers/:id/appointment
- Bereits vergebene Zeiten sind in der Terminauswahl gesperrt ("belegt"), der
  eigene Termin bleibt waehlbar; steht am Ticket schon einer, ist er beim
  Oeffnen vorausgewaehlt
- Zeiten, die jemand von Hand in WOMS gesetzt hat, tauchen mit auf

Sicherung gegen Datenverlust: bevorzugt wird das Akquise-Ticket. Gibt es keins,
kommt nur ein anderes offenes Ticket OHNE Datum in Frage - bei Webpage- und
Projekttickets bedeutet startDate den Projektstart, den wuerden wir sonst still
ueberschreiben. Die Rueckmeldung nennt immer Nummer und Art des Tickets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:09:51 +00:00

373 lines
19 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Rechnungsanalyse / Projektkalkulation.
Bewusst eigenes helles "Organic"-Design (nicht das dunkle Portal-Theme),
weil die Seite im Kundengespraech gezeigt wird. Fonts liegen lokal unter
/fonts (kein Google-CDN-Abruf). */
@font-face {
font-family: 'Caprasimo'; font-style: normal; font-weight: 400; font-display: swap;
src: url(/fonts/caprasimo-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Caprasimo'; font-style: normal; font-weight: 400; font-display: swap;
src: url(/fonts/caprasimo-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Figtree ist ein Variable Font eine Datei deckt 400700 ab. */
@font-face {
font-family: 'Figtree'; font-style: normal; font-weight: 400 700; font-display: swap;
src: url(/fonts/figtree-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Figtree'; font-style: normal; font-weight: 400 700; font-display: swap;
src: url(/fonts/figtree-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
:root {
--color-bg: #f5ead8;
--color-surface: #ebddc5;
--color-text: #201e1d;
--color-accent: #c67139;
--color-accent-2: #7a8a5e;
--color-neutral-100: #f9f4ed;
--color-neutral-200: #eee7db;
--color-neutral-300: #dcd3c4;
--color-neutral-400: #c0b6a5;
--color-neutral-500: #a19786;
--color-neutral-600: #82796a;
--color-neutral-700: #645c50;
--color-neutral-800: #474238;
--color-neutral-900: #2e2b25;
--color-accent-100: #fff2eb;
--color-accent-200: #ffe1d0;
--color-accent-700: #8c491a;
--color-accent-800: #643312;
--color-accent-2-100: #f0fae1;
--color-accent-2-200: #e1eecc;
--color-accent-2-500: #8fa073;
--color-accent-2-600: #728157;
--color-accent-2-700: #56633f;
--color-accent-2-800: #3d472b;
--font-heading: 'Caprasimo', Georgia, serif;
--font-body: 'Figtree', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--radius-md: 16px;
--radius-lg: 28px;
--shadow-sm: 0 1px 2px rgba(46, 43, 37, 0.14);
--shadow-md: 0 3px 10px rgba(46, 43, 37, 0.16);
--shadow-lg: 0 12px 32px rgba(46, 43, 37, 0.22);
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-body);
font-size: 15px;
line-height: 1.55;
}
h1, h2, h3 { font-family: var(--font-heading); font-weight: 400; line-height: 1.12; margin: 0; }
a { color: var(--color-accent-700); }
a:hover { color: var(--color-accent-800); }
button { font: inherit; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: var(--color-accent-200); }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type=number] { -moz-appearance: textfield; }
/* ------------------------------------------------------------- Kopfzeile --- */
.kal-header {
position: sticky; top: 0; z-index: 20;
background: var(--color-accent-2-500); color: var(--color-accent-2-100);
padding: 14px 20px; box-shadow: var(--shadow-md);
}
.kal-header-inner {
max-width: 1040px; margin: 0 auto;
display: flex; align-items: center; gap: clamp(10px, 2.4vw, 18px); flex-wrap: wrap;
}
/* Logo ist zugleich der Zurueck-Link ins Admin-Panel. */
.kal-brand {
font-family: var(--font-heading); font-size: clamp(17px, 4vw, 22px);
color: #fff; text-decoration: none;
}
.kal-brand:hover { color: #fff; opacity: 0.85; }
.kal-spacer { flex: 1; min-width: 0; }
.kal-stat { display: flex; align-items: baseline; gap: 8px; }
.kal-stat-lbl {
font-size: 11.5px; font-weight: 700; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--color-accent-2-200);
}
.kal-stat-val { font-family: var(--font-heading); line-height: 1; color: #fff; font-size: clamp(16px, 3.6vw, 22px); }
.kal-stat-val.big { font-size: clamp(19px, 4.6vw, 30px); }
main { max-width: 1040px; margin: 0 auto; padding: clamp(18px, 3.4vw, 28px) clamp(12px, 3.2vw, 20px) 100px; }
/* ------------------------------------------------------------------ Hero --- */
.hero {
border-radius: var(--radius-lg); background: var(--color-accent-2-500);
color: var(--color-accent-2-100);
padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) clamp(20px, 3.6vw, 30px);
}
.kicker { font-size: 12px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--color-accent-2-200); }
.hero-price-row { display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin-top: 10px; }
.hero-price { font-family: var(--font-heading); font-size: clamp(46px, 11vw, 72px); line-height: 0.95; color: #fff; }
.hero-price-sub { font-size: 15px; color: var(--color-accent-2-200); padding-bottom: 12px; }
.hero-tiles {
display: grid; grid-template-columns: repeat(auto-fit, minmax(clamp(120px, 44%, 210px), 1fr));
gap: 10px; margin-top: 24px;
}
.tile { background: rgba(255, 255, 255, 0.16); border-radius: var(--radius-md); padding: 16px 18px; }
.tile-lbl { font-size: 11.5px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-accent-2-200); }
.tile-val { font-family: var(--font-heading); font-size: 30px; color: #fff; margin-top: 4px; }
.tile-sub { font-size: 12.5px; color: var(--color-accent-2-200); margin-top: 2px; }
.tile-pay { grid-column: 1 / -1; background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.tile-pay .tile-lbl { color: var(--color-accent-2-600); }
.tile-pay-title { font-weight: 700; font-size: 16px; margin-top: 4px; }
.tile-pay-text { font-size: 13px; line-height: 1.45; margin-top: 4px; text-wrap: pretty; }
.flag {
margin-top: 12px; padding: 15px 18px; border-radius: var(--radius-md);
font-size: 13.5px; line-height: 1.5; text-wrap: pretty;
background: rgba(255, 255, 255, 0.16); color: var(--color-accent-2-100);
}
.flag.ok { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.flag.talk { background: var(--color-accent-200); color: var(--color-accent-800); }
.flag.kenso { background: var(--color-neutral-100); color: var(--color-neutral-800); }
.hero-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 20px; }
.btn-solid {
padding: 14px 26px; border-radius: 999px; border: none; background: #fff;
color: var(--color-accent-2-700); font-weight: 700; font-size: 15px; cursor: pointer;
}
.btn-outline {
padding: 14px 22px; border-radius: 999px; border: 2px solid rgba(255, 255, 255, 0.4);
background: transparent; color: var(--color-accent-2-100); font-weight: 700; font-size: 15px; cursor: pointer;
}
.btn-outline:hover { background: rgba(255, 255, 255, 0.14); }
/* -------------------------------------------------------------- Bausteine --- */
.block { margin-top: 38px; }
.block h2 { font-size: clamp(23px, 5.4vw, 32px); }
.block-lead { font-size: 14.5px; color: var(--color-neutral-600); margin: 4px 0 14px; max-width: 620px; text-wrap: pretty; }
.section-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.section-head h2 { flex: 1; min-width: 150px; }
.section-tag {
font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
padding: 6px 13px; border-radius: 999px;
background: var(--color-accent-200); color: var(--color-accent-800);
}
.section-tag.kenso { background: var(--color-accent-2-200); color: var(--color-accent-2-800); }
.group-label {
font-size: 11.5px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
color: var(--color-neutral-500); margin: 22px 0 10px;
}
.chips { display: flex; flex-wrap: wrap; gap: 10px; }
.chip {
display: inline-flex; align-items: center; gap: 9px; padding: 12px 20px; border-radius: 999px;
border: 2px solid var(--color-neutral-300); background: transparent;
color: var(--color-neutral-700); font-weight: 700; font-size: 15px; cursor: pointer; min-height: 48px;
}
.chip.on { border-color: var(--color-accent); background: var(--color-accent-100); color: var(--color-accent-800); }
.chip .mark {
flex: 0 0 20px; height: 20px; border-radius: 999px; border: 2px solid var(--color-neutral-400);
display: flex; align-items: center; justify-content: center; color: #fff; font-size: 11px;
}
.chip.on .mark { border-color: var(--color-accent); background: var(--color-accent); }
.chip .count {
font-weight: 700; font-size: 13px; padding: 2px 9px; border-radius: 999px;
background: var(--color-accent-200); color: var(--color-accent-800);
}
.item-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(clamp(130px, 45%, 300px), 1fr)); gap: 10px; }
.item {
display: flex; align-items: flex-start; gap: clamp(9px, 2vw, 14px);
padding: clamp(13px, 2.6vw, 18px) clamp(14px, 2.8vw, 20px);
border-radius: var(--radius-lg); border: 2px solid var(--color-neutral-200);
background: var(--color-neutral-100); cursor: pointer; min-height: 72px; text-align: left;
color: inherit; width: 100%;
}
.item.on { border-color: var(--color-accent); background: var(--color-accent-100); }
.item .mark {
flex: 0 0 clamp(20px, 5vw, 26px); width: clamp(20px, 5vw, 26px); height: clamp(20px, 5vw, 26px);
border-radius: 999px; border: 2px solid var(--color-neutral-400);
display: flex; align-items: center; justify-content: center;
color: #fff; font-size: 13px; font-weight: 700; margin-top: 2px;
}
.item.on .mark { border-color: var(--color-accent); background: var(--color-accent); }
.item-body { flex: 1; min-width: 0; }
.item-name { display: block; font-weight: 600; font-size: clamp(14px, 2.9vw, 16px); line-height: 1.3; }
.item-desc {
display: block; font-size: clamp(11.5px, 2.4vw, 13px); line-height: 1.45;
color: var(--color-neutral-600); margin-top: 4px; text-wrap: pretty;
}
.item-hrs {
display: inline-block; margin-top: 9px; padding: 4px 11px; border-radius: 999px;
background: var(--color-neutral-200); color: var(--color-neutral-600);
font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.item.on .item-hrs { background: var(--color-accent-200); color: var(--color-accent-800); }
.qty { display: flex; align-items: center; gap: 6px; margin-top: 10px; max-width: 100%; }
.qty button {
flex: 0 0 auto; width: clamp(34px, 9vw, 44px); height: 44px; border-radius: 999px;
border: 2px solid var(--color-neutral-300); background: #fff; color: var(--color-accent-700);
font-size: 20px; font-weight: 700; cursor: pointer; line-height: 1; padding: 0;
}
.qty input {
flex: 1 1 auto; min-width: 0; width: clamp(44px, 13vw, 64px); height: 44px; border-radius: 999px;
border: 2px solid var(--color-neutral-300); background: #fff; font: inherit; font-size: 15px;
font-weight: 700; text-align: center; color: var(--color-text); padding: 0;
}
.empty-hint {
margin-top: 34px; padding: 34px; border-radius: var(--radius-lg);
background: var(--color-neutral-100); border: 2px dashed var(--color-neutral-300); text-align: center;
}
.empty-hint h3 { font-size: 24px; margin-bottom: 6px; }
.empty-hint p { font-size: 14.5px; color: var(--color-neutral-600); margin: 0; }
.panel { margin-top: 38px; border-radius: var(--radius-lg); background: var(--color-surface); padding: clamp(20px, 3.6vw, 30px); }
.panel h2 { font-size: clamp(23px, 5.4vw, 30px); margin: 6px 0 4px; }
.panel-lead { font-size: 14.5px; line-height: 1.55; color: var(--color-neutral-700); margin: 0 0 16px; max-width: 560px; text-wrap: pretty; }
.chosen-chip {
display: inline-flex; align-items: center; gap: 10px; padding: 9px 16px; border-radius: 999px;
background: var(--color-neutral-100); font-size: 14px;
}
.chosen-chip b { color: var(--color-accent-700); font-weight: 700; }
/* ---------------------------------------------------------------- Termin --- */
.seg { display: inline-flex; gap: 4px; padding: 4px; border-radius: 999px; background: var(--color-neutral-200); margin-bottom: 18px; }
.seg button {
padding: 11px 22px; border-radius: 999px; border: none; background: transparent;
color: var(--color-neutral-700); font-weight: 700; font-size: 14.5px; cursor: pointer; min-height: 44px;
}
.seg button.on { background: var(--color-accent); color: #fff; }
.person {
display: flex; align-items: center; gap: 12px; padding: 12px 20px 12px 12px; border-radius: 999px;
border: 2px solid var(--color-neutral-200); background: var(--color-neutral-100);
color: var(--color-text); cursor: pointer; min-height: 52px; text-align: left;
}
.person.on { border-color: var(--color-accent); background: var(--color-accent-100); }
.person .ava {
flex: 0 0 34px; height: 34px; border-radius: 999px; background: var(--color-neutral-400); color: #fff;
display: flex; align-items: center; justify-content: center; font-family: var(--font-heading); font-size: 15px;
}
.person.on .ava { background: var(--color-accent); }
.person b { display: block; font-weight: 700; font-size: 15px; }
.person span.role { display: block; font-size: 12.5px; color: var(--color-neutral-600); }
.day-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(clamp(74px, 22%, 110px), 1fr)); gap: 8px; }
.day {
padding: 12px 6px; border-radius: var(--radius-md); border: 2px solid var(--color-neutral-200);
background: var(--color-neutral-100); color: var(--color-text); cursor: pointer; min-height: 74px;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
.day.on { border-color: var(--color-accent); background: var(--color-accent); color: #fff; }
.day:disabled { background: transparent; color: var(--color-neutral-500); opacity: 0.45; cursor: default; }
.day .wd { font-size: 11.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.day .dm { font-family: var(--font-heading); font-size: 20px; line-height: 1.1; }
.day .free { font-size: 11px; font-weight: 600; }
.slot {
padding: 12px 22px; border-radius: 999px; border: 2px solid var(--color-neutral-200);
background: var(--color-neutral-100); color: var(--color-text);
font-weight: 700; font-size: 15px; cursor: pointer; min-height: 48px;
}
.slot.on { border-color: var(--color-accent); background: var(--color-accent); color: #fff; }
.field-label {
display: block; font-size: 11.5px; font-weight: 700; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--color-neutral-500); margin-bottom: 8px;
}
textarea.kal-input, input.kal-input {
width: 100%; border-radius: var(--radius-md); border: 2px solid var(--color-neutral-300);
background: var(--color-neutral-100); padding: 14px 16px; font: inherit; font-size: 15px;
line-height: 1.5; color: var(--color-text); resize: vertical;
}
textarea.kal-input { min-height: 84px; }
.term-status {
margin-top: 16px; padding: 15px 18px; border-radius: var(--radius-md);
font-size: 14.5px; line-height: 1.5; text-wrap: pretty;
background: var(--color-neutral-100); color: var(--color-neutral-700);
}
.term-status.fixed { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.term-save { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 14px; }
.term-save button:disabled { opacity: 0.45; cursor: not-allowed; }
.term-save-hint { font-size: 13px; color: var(--color-neutral-600); flex: 1; min-width: 200px; text-wrap: pretty; }
.term-save-hint.done { color: var(--color-accent-2-700); font-weight: 700; }
.term-save-hint.error { color: var(--color-accent-800); font-weight: 700; }
.slot:disabled { opacity: 0.4; cursor: not-allowed; text-decoration: line-through; }
.slot.taken-own {
border-color: var(--color-accent-2-500); background: var(--color-accent-2-100);
color: var(--color-accent-2-800); text-decoration: none; opacity: 1;
}
/* ------------------------------------------------------------ Kundenportal --- */
.portal-box {
margin-top: 38px; border-radius: var(--radius-lg); background: var(--color-neutral-100);
border: 2px solid var(--color-neutral-200); padding: clamp(18px, 3.4vw, 28px) clamp(18px, 3.6vw, 30px);
display: flex; gap: clamp(16px, 3vw, 30px); align-items: center; flex-wrap: wrap;
}
.portal-qr { flex: 0 0 auto; background: #fff; border-radius: var(--radius-md); padding: clamp(10px, 2vw, 14px); box-shadow: var(--shadow-sm); }
.portal-qr img { display: block; width: clamp(104px, 26vw, 180px); height: clamp(104px, 26vw, 180px); }
.portal-info { flex: 1; min-width: 190px; }
.portal-info h2 { font-size: 28px; margin: 6px 0 8px; }
.cred-row { display: flex; flex-wrap: wrap; gap: 10px; }
.cred {
background: #fff; border-radius: 999px; padding: 8px 20px;
display: flex; align-items: center; gap: 10px; flex: 1 1 240px;
}
.cred span {
font-size: 11px; font-weight: 700; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--color-neutral-500); white-space: nowrap;
}
.cred input {
flex: 1; min-width: 0; border: none; background: transparent; font: inherit;
font-size: 15.5px; font-weight: 700; color: var(--color-text); padding: 4px 0;
}
.cred input.mono { color: var(--color-accent-700); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
footer { margin-top: 34px; font-size: 12.5px; color: var(--color-neutral-500); line-height: 1.6; }
/* ---------------------------------------------------------------- Dialog --- */
.backdrop {
position: fixed; inset: 0; z-index: 50; background: rgba(32, 30, 29, 0.55);
display: flex; align-items: center; justify-content: center; padding: 20px;
}
.backdrop[hidden] { display: none; }
.dialog {
width: 100%; max-width: 640px; max-height: 84vh; overflow: auto; background: var(--color-bg);
border-radius: var(--radius-lg); padding: 26px 28px; box-shadow: var(--shadow-lg);
}
.dialog h2 { font-size: 26px; margin-bottom: 12px; }
.dialog textarea {
width: 100%; height: 340px; border-radius: var(--radius-md); border: 2px solid var(--color-neutral-300);
background: var(--color-neutral-100); padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.55;
color: var(--color-text); resize: vertical;
}
.dialog-actions { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.btn-accent {
padding: 13px 24px; border-radius: 999px; border: none; background: var(--color-accent);
color: #fff; font-weight: 700; font-size: 15px; cursor: pointer;
}
.btn-quiet {
padding: 13px 22px; border-radius: 999px; border: 2px solid var(--color-neutral-300);
background: transparent; color: var(--color-neutral-700); font-weight: 700; font-size: 15px; cursor: pointer;
}
[hidden] { display: none !important; }
@media print {
.kal-header, .hero-actions, .seg, .qty, .backdrop { display: none !important; }
body { background: #fff; }
.hero { color: #201e1d; background: var(--color-accent-2-100); }
.hero-price, .tile-val, .kal-brand { color: var(--color-accent-2-800); }
}