Tower: upload laundry_management 19.0.19.0.4 (via marketplace)
This commit is contained in:
994
addons/laundry_management/static/src/scss/laundry_pos.scss
Normal file
994
addons/laundry_management/static/src/scss/laundry_pos.scss
Normal file
@@ -0,0 +1,994 @@
|
||||
// ============================================================================
|
||||
// Laundry Management — POS / Backend Design System
|
||||
// 8px grid, soft shadows, premium pill components.
|
||||
// ============================================================================
|
||||
|
||||
// ── Tokens ─────────────────────────────────────────────────────────────────
|
||||
$lm-space-1: 4px;
|
||||
$lm-space-2: 8px;
|
||||
$lm-space-3: 12px;
|
||||
$lm-space-4: 16px;
|
||||
$lm-space-5: 24px;
|
||||
|
||||
$lm-radius-sm: 6px;
|
||||
$lm-radius-md: 10px;
|
||||
$lm-radius-lg: 14px;
|
||||
|
||||
$lm-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
$lm-shadow-md: 0 4px 12px rgba(15, 23, 42, 0.08), 0 2px 4px rgba(15, 23, 42, 0.05);
|
||||
$lm-shadow-lg: 0 10px 30px rgba(15, 23, 42, 0.12), 0 4px 8px rgba(15, 23, 42, 0.06);
|
||||
|
||||
$lm-color-urgent: #EF4444;
|
||||
$lm-color-delivery: #10B981;
|
||||
$lm-color-vip: #8B5CF6;
|
||||
$lm-color-normal: #6B7280;
|
||||
$lm-color-type: #4F46E5;
|
||||
|
||||
$lm-font-xs: 11px;
|
||||
$lm-font-sm: 13px;
|
||||
$lm-font-md: 14px;
|
||||
$lm-font-lg: 16px;
|
||||
$lm-font-xl: 18px;
|
||||
|
||||
$lm-bg-card: #FFFFFF;
|
||||
$lm-bg-soft: #F8FAFC;
|
||||
$lm-border: #E2E8F0;
|
||||
$lm-text: #0F172A;
|
||||
$lm-text-muted: #64748B;
|
||||
|
||||
// ── Pill component (shared across panel / kanban / receipt) ───────────────
|
||||
.laundry-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: $lm-space-1 $lm-space-3;
|
||||
border-radius: 9999px;
|
||||
font-size: $lm-font-sm;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: #fff;
|
||||
background-color: $lm-color-normal;
|
||||
box-shadow: $lm-shadow-sm;
|
||||
white-space: nowrap;
|
||||
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
||||
|
||||
&:hover { transform: translateY(-1px); box-shadow: $lm-shadow-md; }
|
||||
|
||||
.fa { font-size: 0.95em; }
|
||||
|
||||
&--type { background-color: $lm-color-type; }
|
||||
&--attr { background-color: $lm-color-normal; }
|
||||
&--urgent { background-color: $lm-color-urgent; }
|
||||
&--delivery { background-color: $lm-color-delivery; }
|
||||
&--vip { background-color: $lm-color-vip; }
|
||||
&--deferred { background-color: #F59E0B; }
|
||||
|
||||
// Semantic data overrides — win over default --attr
|
||||
&[data-priority="urgent"] { background-color: $lm-color-urgent; }
|
||||
&[data-delivery="1"] { background-color: $lm-color-delivery; }
|
||||
}
|
||||
|
||||
// ── Settle Due Banner (POS Mode indicator) ────────────────────────────────
|
||||
.laundry-settle-banner {
|
||||
margin: $lm-space-2 $lm-space-3 0 $lm-space-3;
|
||||
padding: $lm-space-3 $lm-space-4;
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #F97316 100%);
|
||||
color: #fff;
|
||||
border-radius: $lm-radius-md;
|
||||
box-shadow: $lm-shadow-md;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $lm-space-3;
|
||||
animation: laundry-settle-banner-pulse 2.4s ease-in-out infinite;
|
||||
|
||||
&__lead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $lm-space-3;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: $lm-font-xl;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: $lm-font-md;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.06em;
|
||||
line-height: 1.15;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: $lm-space-3;
|
||||
margin-top: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__partner {
|
||||
font-size: $lm-font-sm;
|
||||
font-weight: 600;
|
||||
opacity: 0.95;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 40ch;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-size: $lm-font-md;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.01em;
|
||||
padding: 1px 8px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: $lm-radius-sm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: $lm-font-xs;
|
||||
opacity: 0.85;
|
||||
margin-top: 2px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&__exit {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
color: #fff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
padding: $lm-space-2 $lm-space-3;
|
||||
border-radius: $lm-radius-sm;
|
||||
font-size: $lm-font-sm;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.15s ease, transform 0.12s ease;
|
||||
|
||||
&:hover { background: rgba(255, 255, 255, 0.3); transform: translateY(-1px); }
|
||||
&:active { transform: translateY(0); }
|
||||
&:focus-visible {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes laundry-settle-banner-pulse {
|
||||
0%, 100% { box-shadow: 0 4px 12px rgba(245, 158, 11, 0.35); }
|
||||
50% { box-shadow: 0 4px 20px rgba(245, 158, 11, 0.65); }
|
||||
}
|
||||
|
||||
// ── Global lock of POS chrome while settle-due mode is active ─────────────
|
||||
// The banner component toggles `pos-laundry-settle-active` on <body>.
|
||||
// CSS is a clarity layer, not the safety layer — JS (pos_store_patch /
|
||||
// order_tabs_patch / navbar_patch) is the real gate. But here we also
|
||||
// disable pointer-events on the peripherals because SINGLE-ORDER MODE
|
||||
// guarantees the only tab is the settle order itself; everything else
|
||||
// is visual chrome that shouldn't respond to clicks.
|
||||
body.pos-laundry-settle-active {
|
||||
// Every non-active tab (should be zero under single-order mode —
|
||||
// but defense in depth): hatched, dim, unclickable.
|
||||
.floating-order-container .btn:not(.active) {
|
||||
pointer-events: none;
|
||||
opacity: 0.35;
|
||||
filter: saturate(0.3);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: repeating-linear-gradient(
|
||||
135deg,
|
||||
rgba(245, 158, 11, 0.0) 0 6px,
|
||||
rgba(245, 158, 11, 0.15) 6px 12px
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Active settle tab — keep visible & highlighted in warning orange.
|
||||
.floating-order-container .btn.active {
|
||||
outline: 2px solid #F59E0B;
|
||||
outline-offset: 2px;
|
||||
pointer-events: none; // the tab IS the current order; no switch needed
|
||||
}
|
||||
|
||||
// "+" new-order button inside ListContainer — hard-disabled visually.
|
||||
// JS addNewOrder patch also blocks it.
|
||||
.list-container-add,
|
||||
.o_list_container_add,
|
||||
button[title*="Add a new order" i],
|
||||
button[aria-label*="Add a new order" i] {
|
||||
pointer-events: none;
|
||||
opacity: 0.35;
|
||||
filter: saturate(0.3);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Order Context Panel (POS right-side card) ─────────────────────────────
|
||||
.laundry-context-panel {
|
||||
margin: $lm-space-2 $lm-space-3;
|
||||
padding: $lm-space-3 $lm-space-4;
|
||||
background: $lm-bg-card;
|
||||
border: 1px solid $lm-border;
|
||||
border-radius: $lm-radius-md;
|
||||
box-shadow: $lm-shadow-sm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $lm-space-2;
|
||||
transition: box-shadow 0.18s ease, transform 0.18s ease;
|
||||
|
||||
&:hover { box-shadow: $lm-shadow-md; }
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: $lm-font-xs;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: $lm-text-muted;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $lm-text-muted;
|
||||
padding: $lm-space-1 $lm-space-2;
|
||||
border-radius: $lm-radius-sm;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, color 0.12s ease;
|
||||
|
||||
&:hover { background: $lm-bg-soft; color: $lm-text; }
|
||||
&:focus-visible {
|
||||
outline: 2px solid rgba(99, 102, 241, 0.45);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $lm-space-2;
|
||||
}
|
||||
|
||||
&__attrs .laundry-pill { font-size: $lm-font-xs; padding: 2px $lm-space-2; }
|
||||
|
||||
&__empty {
|
||||
font-size: $lm-font-sm;
|
||||
color: $lm-text-muted;
|
||||
background: $lm-bg-soft;
|
||||
padding: $lm-space-2 $lm-space-3;
|
||||
border-radius: $lm-radius-sm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__cta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $lm-space-2;
|
||||
width: 100%;
|
||||
padding: $lm-space-3 $lm-space-4;
|
||||
background: linear-gradient(135deg, #4F46E5 0%, #6366F1 100%);
|
||||
color: #fff;
|
||||
font-size: $lm-font-md;
|
||||
font-weight: 600;
|
||||
border: 0;
|
||||
border-radius: $lm-radius-md;
|
||||
box-shadow: $lm-shadow-sm;
|
||||
cursor: pointer;
|
||||
transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
|
||||
|
||||
.fa { font-size: 1.05em; }
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: $lm-shadow-md;
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
&:active { transform: translateY(0); filter: brightness(0.95); }
|
||||
&:focus-visible {
|
||||
outline: 2px solid rgba(99, 102, 241, 0.55);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-label {
|
||||
margin-inline-start: $lm-space-1;
|
||||
font-size: $lm-font-xs;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__delivery {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $lm-space-1;
|
||||
padding-top: $lm-space-2;
|
||||
border-top: 1px dashed $lm-border;
|
||||
}
|
||||
|
||||
&__delivery-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $lm-space-2;
|
||||
font-size: $lm-font-sm;
|
||||
color: $lm-text;
|
||||
|
||||
.fa { color: $lm-color-delivery; width: 14px; text-align: center; }
|
||||
}
|
||||
|
||||
&[data-empty="1"] { background: $lm-bg-soft; }
|
||||
&[data-delivery="1"] { border-left: 3px solid $lm-color-delivery; }
|
||||
}
|
||||
|
||||
// ── Popup polish (shared by all 3 laundry popups) ─────────────────────────
|
||||
.modal .modal-dialog {
|
||||
.btn.btn-outline-primary,
|
||||
.btn.btn-primary {
|
||||
border-radius: $lm-radius-md;
|
||||
transition: transform 0.12s ease, box-shadow 0.12s ease,
|
||||
background-color 0.12s ease, border-color 0.12s ease;
|
||||
}
|
||||
|
||||
.btn.btn-primary {
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.18);
|
||||
}
|
||||
|
||||
.btn.btn-outline-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: $lm-shadow-md;
|
||||
}
|
||||
|
||||
.modal-footer .btn-primary,
|
||||
.modal-footer .btn-secondary {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Receipt details (printer-friendly) ────────────────────────────────────
|
||||
.laundry-receipt-details {
|
||||
margin-top: $lm-space-2;
|
||||
text-align: center;
|
||||
|
||||
&__sep {
|
||||
border-top: 1px dashed #000;
|
||||
margin: $lm-space-2 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 700;
|
||||
font-size: $lm-font-md;
|
||||
margin-bottom: $lm-space-1;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: $lm-space-3;
|
||||
font-size: $lm-font-sm;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
min-width: 80px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
&__value {
|
||||
text-align: end;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__chip {
|
||||
display: inline-block;
|
||||
padding: 0 $lm-space-2;
|
||||
margin: 0 2px;
|
||||
border: 1px solid #000;
|
||||
border-radius: $lm-radius-sm;
|
||||
font-size: $lm-font-xs;
|
||||
}
|
||||
}
|
||||
|
||||
// RTL safety
|
||||
[dir="rtl"] .laundry-receipt-details__label { text-align: end; }
|
||||
[dir="rtl"] .laundry-receipt-details__value { text-align: start; }
|
||||
|
||||
// ── Operational Control Board (laundry.order kanban) ──────────────────────
|
||||
.laundry-board {
|
||||
.laundry-board__card {
|
||||
position: relative;
|
||||
background: $lm-bg-card;
|
||||
border: 1px solid $lm-border;
|
||||
border-radius: $lm-radius-md;
|
||||
box-shadow: $lm-shadow-sm;
|
||||
padding: $lm-space-3 $lm-space-4 $lm-space-3 ($lm-space-4 + 4px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $lm-space-2;
|
||||
transition: box-shadow 0.18s ease, transform 0.18s ease;
|
||||
|
||||
&:hover { box-shadow: $lm-shadow-md; transform: translateY(-1px); }
|
||||
}
|
||||
|
||||
.laundry-board__strip {
|
||||
position: absolute;
|
||||
left: 0; top: 0; bottom: 0;
|
||||
width: 4px;
|
||||
border-top-left-radius: $lm-radius-md;
|
||||
border-bottom-left-radius: $lm-radius-md;
|
||||
background: $lm-color-normal;
|
||||
}
|
||||
|
||||
.laundry-board__card[data-state="intake"] .laundry-board__strip { background: #3B82F6; }
|
||||
.laundry-board__card[data-state="processing"] .laundry-board__strip { background: #F59E0B; }
|
||||
.laundry-board__card[data-state="ready"] .laundry-board__strip { background: $lm-color-delivery; }
|
||||
.laundry-board__card[data-state="delivered"] .laundry-board__strip { background: $lm-color-normal; }
|
||||
.laundry-board__card[data-priority="urgent"] .laundry-board__strip { background: $lm-color-urgent; }
|
||||
.laundry-board__card[data-priority="urgent"] { border-color: rgba(239, 68, 68, 0.35); }
|
||||
|
||||
.laundry-board__head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.laundry-board__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $lm-space-2;
|
||||
}
|
||||
|
||||
.laundry-board__name {
|
||||
font-size: $lm-font-md;
|
||||
color: $lm-text;
|
||||
}
|
||||
|
||||
.laundry-board__total {
|
||||
font-size: $lm-font-md;
|
||||
font-weight: 700;
|
||||
color: $lm-text;
|
||||
}
|
||||
|
||||
.laundry-board__customer {
|
||||
font-size: $lm-font-sm;
|
||||
color: $lm-text-muted;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.laundry-board__badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $lm-space-1;
|
||||
}
|
||||
|
||||
.laundry-board__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $lm-space-3;
|
||||
font-size: $lm-font-xs;
|
||||
color: $lm-text-muted;
|
||||
}
|
||||
|
||||
.laundry-board__due {
|
||||
color: $lm-color-urgent;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.laundry-board__actions { margin-top: $lm-space-1; }
|
||||
}
|
||||
|
||||
|
||||
// ── ProductConfiguratorPopup — laundry enhancement ─────────────────────
|
||||
// Scoped exclusively under `.popup-product-configurator.laundry-enhanced`,
|
||||
// applied to the modal-content via Dialog's `contentClass` prop from
|
||||
// xml/product_configurator_popup.xml. Non-laundry configurator popups are
|
||||
// unaffected.
|
||||
.popup-product-configurator.laundry-enhanced {
|
||||
|
||||
// Attribute group — stronger title, better spacing.
|
||||
.modal-body .attribute {
|
||||
margin-bottom: $lm-space-4 !important;
|
||||
padding-bottom: $lm-space-3;
|
||||
border-bottom: 1px dashed $lm-border;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.attribute_name {
|
||||
font-size: $lm-font-sm;
|
||||
font-weight: 800 !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.09em;
|
||||
color: $lm-text-muted;
|
||||
margin-bottom: $lm-space-3 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Touch-first tiles — applies to both Radio and Pills renderers,
|
||||
// which share the same `.configurator_radio > .attribute-name-cell`
|
||||
// structure in core.
|
||||
.configurator_radio {
|
||||
> .d-flex {
|
||||
flex-wrap: wrap !important;
|
||||
gap: $lm-space-3 !important;
|
||||
}
|
||||
|
||||
.attribute-name-cell {
|
||||
flex: 1 1 calc(33% - #{$lm-space-3});
|
||||
min-width: 140px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
// Hide the native radio — the label IS the tile.
|
||||
.form-check-input,
|
||||
.radio-check {
|
||||
position: absolute !important;
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
> label {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: $lm-space-2;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
padding: 12px 16px !important;
|
||||
border: 2px solid $lm-border !important;
|
||||
border-radius: 12px !important;
|
||||
background: #fff !important;
|
||||
color: $lm-text !important;
|
||||
font-size: $lm-font-md;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-shadow: none !important;
|
||||
transition: transform 0.14s ease,
|
||||
border-color 0.14s ease,
|
||||
box-shadow 0.16s ease,
|
||||
background 0.14s ease,
|
||||
color 0.14s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: $lm-shadow-sm !important;
|
||||
}
|
||||
&:active { transform: translateY(0); }
|
||||
&:focus-visible {
|
||||
outline: 2px solid rgba(79, 70, 229, 0.45);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
// Wrap text span so conflict styling still readable.
|
||||
> span:first-child { word-break: break-word; }
|
||||
}
|
||||
}
|
||||
|
||||
// Selected state:
|
||||
// - Pills renderer: core toggles `.active` on the <label>.
|
||||
// - Radio renderer: input is :checked and precedes label.
|
||||
.attribute-name-cell > input:checked + label,
|
||||
.attribute-name-cell > label.active {
|
||||
border-color: $lm-color-type !important;
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.14),
|
||||
$lm-shadow-sm !important;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(79, 70, 229, 0.08) 0%,
|
||||
rgba(79, 70, 229, 0.00) 100%) !important;
|
||||
color: $lm-color-type !important;
|
||||
|
||||
> span:first-child { color: $lm-color-type !important; }
|
||||
|
||||
&::after {
|
||||
content: "\f00c"; // FontAwesome check
|
||||
font-family: "FontAwesome", "Font Awesome 6 Free", sans-serif;
|
||||
font-weight: 900;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: $lm-color-type;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
box-shadow: $lm-shadow-sm;
|
||||
}
|
||||
}
|
||||
|
||||
// Price extra chip — stronger, more scannable.
|
||||
.price-extra-cell {
|
||||
margin: 0 !important;
|
||||
|
||||
.price_extra {
|
||||
font-size: $lm-font-xs !important;
|
||||
font-weight: 800 !important;
|
||||
padding: 2px 10px !important;
|
||||
border-radius: 9999px !important;
|
||||
background: rgba(245, 158, 11, 0.18) !important;
|
||||
color: #B45309 !important;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
}
|
||||
|
||||
// Conflict state (core adds `text-muted` on the inner span).
|
||||
.attribute-name-cell > label > span.text-muted {
|
||||
opacity: 0.6;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
// Custom-value text input — keep larger, consistent.
|
||||
.custom-value-cell {
|
||||
flex: 1 1 100%;
|
||||
|
||||
.form-control { height: 48px; font-size: $lm-font-md; }
|
||||
}
|
||||
}
|
||||
|
||||
// CTA footer — bigger, bolder, full width on tablet.
|
||||
.modal-footer {
|
||||
padding: $lm-space-3 $lm-space-4;
|
||||
|
||||
> .d-flex {
|
||||
gap: $lm-space-3 !important;
|
||||
}
|
||||
|
||||
.btn.btn-primary,
|
||||
.btn.btn-secondary {
|
||||
min-height: 56px;
|
||||
font-size: $lm-font-lg;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: $lm-radius-md;
|
||||
}
|
||||
|
||||
.btn.btn-primary {
|
||||
box-shadow: 0 2px 8px rgba(79, 70, 229, 0.22);
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
box-shadow: 0 4px 14px rgba(79, 70, 229, 0.32);
|
||||
}
|
||||
&.disabled {
|
||||
opacity: 0.55;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Full-width primary button below tablet breakpoint.
|
||||
@media (max-width: 768px) {
|
||||
.modal-footer > .d-flex {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
.btn { width: 100% !important; }
|
||||
}
|
||||
}
|
||||
|
||||
// Archived-combination banner — keep legible but calmer.
|
||||
.alert.alert-warning {
|
||||
border-radius: $lm-radius-md;
|
||||
border: 0;
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
color: #92400E;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Laundry Orders POS popup ──────────────────────────────────────────
|
||||
// Scoped to `.laundry-orders-popup` (applied to .modal-content via the
|
||||
// Dialog `contentClass` prop). Touches only modal contents — no leak
|
||||
// to other POS popups.
|
||||
.laundry-orders-popup {
|
||||
|
||||
.laundry-orders-popup__body {
|
||||
padding: $lm-space-3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $lm-space-3;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.laundry-orders-popup__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $lm-space-3;
|
||||
padding-bottom: $lm-space-2;
|
||||
border-bottom: 1px solid $lm-border;
|
||||
}
|
||||
|
||||
.laundry-orders-popup__partner {
|
||||
font-size: $lm-font-lg;
|
||||
font-weight: 700;
|
||||
color: $lm-text;
|
||||
|
||||
.fa { color: $lm-color-type; }
|
||||
}
|
||||
|
||||
.laundry-orders-popup__phone {
|
||||
font-size: $lm-font-sm;
|
||||
font-weight: 500;
|
||||
color: $lm-text-muted;
|
||||
|
||||
.fa { color: $lm-text-muted; font-size: 0.85em; }
|
||||
}
|
||||
|
||||
.laundry-orders-popup__count { font-size: $lm-font-sm; }
|
||||
|
||||
.laundry-orders-popup__search {
|
||||
margin-bottom: 0;
|
||||
.input-group .btn { font-weight: 600; }
|
||||
}
|
||||
|
||||
.laundry-orders-popup__error {
|
||||
margin-bottom: 0;
|
||||
border-radius: $lm-radius-md;
|
||||
}
|
||||
|
||||
// Loading skeleton
|
||||
.laundry-orders-popup__loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $lm-space-2;
|
||||
}
|
||||
.laundry-orders-popup__skeleton {
|
||||
height: 96px;
|
||||
border-radius: $lm-radius-md;
|
||||
background: linear-gradient(90deg,
|
||||
$lm-bg-soft 0%, #ECF0F4 50%, $lm-bg-soft 100%);
|
||||
background-size: 200% 100%;
|
||||
animation: laundry-skeleton-shimmer 1.4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes laundry-skeleton-shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
// Empty state
|
||||
.laundry-orders-popup__empty {
|
||||
background: $lm-bg-soft;
|
||||
border: 1px dashed $lm-border;
|
||||
border-radius: $lm-radius-md;
|
||||
.fa { color: $lm-text-muted; opacity: 0.5; }
|
||||
}
|
||||
|
||||
// Card list
|
||||
.laundry-orders-popup__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $lm-space-3;
|
||||
}
|
||||
.laundry-orders-popup__card {
|
||||
background: $lm-bg-card;
|
||||
border: 1px solid $lm-border;
|
||||
border-left: 4px solid $lm-color-normal;
|
||||
border-radius: $lm-radius-md;
|
||||
padding: $lm-space-3 $lm-space-4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $lm-space-2;
|
||||
box-shadow: $lm-shadow-sm;
|
||||
transition: box-shadow 0.16s ease;
|
||||
|
||||
&:hover { box-shadow: $lm-shadow-md; }
|
||||
|
||||
&[data-state="intake"] { border-left-color: #3B82F6; }
|
||||
&[data-state="processing"] { border-left-color: #F59E0B; }
|
||||
&[data-state="ready"] { border-left-color: $lm-color-delivery; }
|
||||
&[data-state="delivered"] { border-left-color: $lm-color-normal; }
|
||||
&[data-state="cancelled"] {
|
||||
border-left-color: $lm-text-muted;
|
||||
opacity: 0.75;
|
||||
}
|
||||
&[data-payment="due"] { border-left-color: $lm-color-urgent; }
|
||||
|
||||
&[data-busy="1"] { opacity: 0.7; pointer-events: none; }
|
||||
}
|
||||
|
||||
.laundry-orders-popup__card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: $lm-space-3;
|
||||
}
|
||||
.laundry-orders-popup__order-name {
|
||||
font-size: $lm-font-md;
|
||||
font-weight: 800;
|
||||
color: $lm-text;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.laundry-orders-popup__pos-ref {
|
||||
font-size: $lm-font-xs;
|
||||
color: $lm-text-muted;
|
||||
font-weight: 500;
|
||||
}
|
||||
.laundry-orders-popup__date {
|
||||
font-size: $lm-font-xs;
|
||||
color: $lm-text-muted;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.laundry-orders-popup__card-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: $lm-space-3;
|
||||
font-size: $lm-font-sm;
|
||||
color: $lm-text;
|
||||
.fa { color: $lm-text-muted; }
|
||||
}
|
||||
.laundry-orders-popup__services {
|
||||
font-size: $lm-font-xs;
|
||||
max-width: 280px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.laundry-orders-popup__total { font-weight: 700; font-size: $lm-font-md; }
|
||||
.laundry-orders-popup__due { color: $lm-color-urgent; font-weight: 700; }
|
||||
|
||||
// Badges
|
||||
.laundry-orders-popup__badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $lm-space-2;
|
||||
}
|
||||
.laundry-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px $lm-space-2;
|
||||
font-size: $lm-font-xs;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
border-radius: 9999px;
|
||||
white-space: nowrap;
|
||||
line-height: 1.3;
|
||||
.fa { font-size: 0.85em; }
|
||||
}
|
||||
.badge-state-intake { background: rgba(59,130,246,.12); color: #1E40AF; }
|
||||
.badge-state-processing { background: rgba(245,158,11,.14); color: #92400E; }
|
||||
.badge-state-ready { background: rgba(16,185,129,.14); color: #065F46; }
|
||||
.badge-state-delivered { background: rgba(107,114,128,.16); color: #1F2937; }
|
||||
.badge-state-cancelled { background: rgba(107,114,128,.10); color: $lm-text-muted; }
|
||||
.badge-payment-paid { background: rgba(16,185,129,.16); color: #065F46; }
|
||||
.badge-payment-deferred { background: rgba(245,158,11,.16); color: #92400E; }
|
||||
.badge-payment-settled { background: rgba(99,102,241,.12); color: $lm-color-type; }
|
||||
.badge-payment-due { background: rgba(239,68,68,.16); color: #991B1B; }
|
||||
.laundry-badge--delivery {
|
||||
background: rgba(16,185,129,.14);
|
||||
color: $lm-color-delivery;
|
||||
}
|
||||
.laundry-badge--source {
|
||||
background: $lm-bg-soft;
|
||||
color: $lm-text-muted;
|
||||
}
|
||||
|
||||
// Actions row
|
||||
.laundry-orders-popup__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $lm-space-2;
|
||||
padding-top: $lm-space-2;
|
||||
border-top: 1px dashed $lm-border;
|
||||
|
||||
.btn {
|
||||
min-height: 44px; // touch target
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
padding-inline: $lm-space-3;
|
||||
}
|
||||
}
|
||||
|
||||
.laundry-orders-popup__due-hint { margin-top: 2px; }
|
||||
}
|
||||
|
||||
// ── Thermal Work Order receipt ─────────────────────────────────────────
|
||||
// Rendered by LaundryWorkOrderThermal when sent through pos.printer.print.
|
||||
// Sized for an 80 mm POS printer; @media print collapses to full-width
|
||||
// for the browser fallback (`webPrintFallback: true`).
|
||||
.laundry-thermal {
|
||||
font-family: "Courier New", "Courier", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
width: 76mm;
|
||||
padding: 4mm;
|
||||
color: #000;
|
||||
background: #fff;
|
||||
|
||||
&__header { text-align: center; margin-bottom: 4px; }
|
||||
&__company { font-weight: 700; font-size: 13px; }
|
||||
&__title {
|
||||
font-weight: 800; font-size: 16px;
|
||||
margin: 4px 0 2px; letter-spacing: 0.06em;
|
||||
}
|
||||
&__name { font-size: 13px; font-weight: 700; }
|
||||
|
||||
&__divider { border-top: 1px dashed #000; margin: 4px 0; }
|
||||
|
||||
&__row {
|
||||
display: flex; justify-content: space-between; gap: 8px;
|
||||
margin: 1px 0;
|
||||
}
|
||||
&__label { font-weight: 700; }
|
||||
|
||||
&__lines {
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
border-collapse: collapse;
|
||||
|
||||
th, td { padding: 2px 4px; vertical-align: top; text-align: left; }
|
||||
th { border-bottom: 1px solid #000; font-weight: 700; }
|
||||
td.qty, th.qty { width: 28px; text-align: center; }
|
||||
td.amount, th.amount { text-align: right; white-space: nowrap; }
|
||||
}
|
||||
|
||||
&__tracking { font-size: 10px; color: #555; margin-top: 1px; }
|
||||
|
||||
&__totals { margin-top: 4px; }
|
||||
&__total { font-weight: 700; }
|
||||
&__due {
|
||||
font-weight: 800;
|
||||
border-top: 1px solid #000;
|
||||
padding-top: 2px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__status {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
margin-top: 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
// Browser-fallback path — let the receipt fill the printable area.
|
||||
.laundry-thermal { width: 100%; max-width: 80mm; margin: 0 auto; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user