Enhances Services module with detail view and interactions
Adds comprehensive service detail view with multiple tabs and dynamic interactions Implements client-side navigation between service list and detail views Introduces mock service data catalog for flexible component rendering Extends localization support for new service detail screens Improves user experience by adding edit capabilities and smooth view transitions
This commit is contained in:
parent
fad5e46dfb
commit
120367acbb
22 changed files with 1780 additions and 597 deletions
|
|
@ -557,6 +557,10 @@ swp-section-label {
|
|||
padding-bottom: var(--spacing-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin-bottom: var(--spacing-6);
|
||||
|
||||
&.spaced {
|
||||
margin-top: var(--spacing-6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Section header - wrapper when action link is needed */
|
||||
|
|
@ -727,6 +731,281 @@ swp-form-input {
|
|||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
&.date-range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-3);
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
DRAWER FORM PATTERNS
|
||||
Scoped styles for forms inside drawers
|
||||
=========================================== */
|
||||
[data-drawer] {
|
||||
/* Form row - vertical layout */
|
||||
swp-form-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.spaced {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form labels - uppercase style */
|
||||
swp-form-label {
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
.optional,
|
||||
.auto {
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* Form value - read-only display */
|
||||
swp-form-value {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* Form divider */
|
||||
swp-form-divider {
|
||||
display: block;
|
||||
height: 1px;
|
||||
background: var(--color-border);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* Form hint text */
|
||||
swp-form-hint {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-muted);
|
||||
margin: -8px 0 16px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Form group - gray card background */
|
||||
swp-form-group {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
background: var(--color-background-alt);
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
|
||||
swp-form-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form select wrapper */
|
||||
swp-form-select {
|
||||
display: block;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Text inputs */
|
||||
input[type="text"],
|
||||
input[type="date"] {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
/* Textarea */
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface);
|
||||
resize: vertical;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
/* Date range inputs */
|
||||
swp-date-range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.5;
|
||||
background: var(--color-background-alt);
|
||||
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* Drawer header with background */
|
||||
swp-drawer-header {
|
||||
background: var(--color-background-alt);
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
swp-drawer-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Drawer body padding */
|
||||
swp-drawer-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Drawer footer with background */
|
||||
swp-drawer-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
background: var(--color-background-alt);
|
||||
|
||||
swp-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
DRAWER DATA ROWS (checkbox + label + input)
|
||||
For rate-style drawer content
|
||||
=========================================== */
|
||||
[data-drawer] swp-data-table {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 1fr 100px;
|
||||
}
|
||||
|
||||
[data-drawer] swp-data-row {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
[data-drawer] swp-data-label {
|
||||
font-size: var(--font-size-base);
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
[data-drawer] swp-data-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-self: end;
|
||||
gap: 4px;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
input {
|
||||
width: 100px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.disabled input {
|
||||
opacity: 0.4;
|
||||
background: var(--color-background);
|
||||
}
|
||||
}
|
||||
|
||||
[data-drawer] swp-section-label {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
[data-drawer] swp-data-section {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
|
|
@ -751,3 +1030,271 @@ swp-empty-state {
|
|||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
TAGS (Generic)
|
||||
=========================================== */
|
||||
swp-tags-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
swp-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-1);
|
||||
padding: var(--spacing-1) var(--spacing-3);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-background);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
&.master {
|
||||
background: var(--bg-purple-strong);
|
||||
color: var(--color-purple);
|
||||
}
|
||||
|
||||
&.senior {
|
||||
background: var(--bg-blue-strong);
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
&.junior {
|
||||
background: var(--bg-amber-strong);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
&.cert {
|
||||
background: var(--bg-teal-strong);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
&.popular {
|
||||
background: var(--bg-amber-strong);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
&.combo {
|
||||
background: var(--bg-teal-strong);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
&.color {
|
||||
background: var(--bg-purple-strong);
|
||||
color: var(--color-purple);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
STATUS INDICATOR (Generic)
|
||||
=========================================== */
|
||||
swp-status-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
padding: var(--spacing-2) var(--spacing-4);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
margin-left: auto;
|
||||
|
||||
&[data-active="true"] {
|
||||
background: var(--bg-green-strong);
|
||||
color: var(--color-green);
|
||||
border: 1px solid var(--bg-green-border);
|
||||
}
|
||||
|
||||
&[data-active="false"] {
|
||||
background: var(--bg-red-medium);
|
||||
color: var(--color-red);
|
||||
border: 1px solid var(--bg-red-border);
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
FACT BOXES (Inline)
|
||||
=========================================== */
|
||||
swp-fact-boxes-inline {
|
||||
display: flex;
|
||||
gap: var(--spacing-12);
|
||||
margin-top: var(--spacing-1);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
swp-fact-inline {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-2);
|
||||
|
||||
swp-fact-inline-value {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
swp-fact-inline-label {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
EDIT SECTION (Grid + Subgrid)
|
||||
=========================================== */
|
||||
swp-edit-section {
|
||||
display: grid;
|
||||
grid-template-columns: 140px 1fr;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
swp-edit-row {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
font-size: var(--font-size-base);
|
||||
padding: var(--spacing-4) var(--spacing-5);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-background-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
transition: all var(--transition-fast);
|
||||
cursor: text;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: var(--color-surface);
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
|
||||
&[data-type="number"] {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
width: 150px;
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
swp-edit-label {
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
swp-edit-value {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
padding: var(--spacing-4) var(--spacing-5);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-background-alt);
|
||||
border: 1px solid transparent;
|
||||
transition: all var(--transition-fast);
|
||||
cursor: text;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: var(--color-surface);
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
|
||||
&.mono {
|
||||
font-family: var(--font-mono);
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
swp-edit-select {
|
||||
display: block;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: var(--spacing-2) var(--spacing-4);
|
||||
font-size: var(--font-size-base);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
VIEW TRANSITIONS (List/Detail swap)
|
||||
=========================================== */
|
||||
.view-fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.view-fade-in {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
BACK LINK
|
||||
=========================================== */
|
||||
swp-back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-sm);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
DETAIL GRID (2-column layout)
|
||||
=========================================== */
|
||||
swp-detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-8);
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
swp-detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,3 +181,95 @@ swp-notification-intro {
|
|||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-5);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
SELECT DROPDOWN (Popover API)
|
||||
=========================================== */
|
||||
swp-select {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
swp-select button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-3);
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
font-size: var(--font-size-base);
|
||||
font-family: inherit;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-background-alt);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 150ms ease;
|
||||
min-width: 160px;
|
||||
anchor-name: --select-trigger;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
swp-select-value {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
swp-select button i {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-base);
|
||||
transition: transform 150ms ease;
|
||||
}
|
||||
|
||||
swp-select button[aria-expanded="true"] i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
swp-select [popover] {
|
||||
position: absolute;
|
||||
position-anchor: --select-trigger;
|
||||
top: anchor(bottom);
|
||||
left: anchor(left);
|
||||
margin: var(--spacing-1) 0 0 0;
|
||||
padding: var(--spacing-2);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
min-width: anchor-size(width);
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
swp-select [popover]:popover-open {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
swp-select-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text);
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--bg-teal-subtle);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,9 @@ swp-drawer-body {
|
|||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-5);
|
||||
}
|
||||
|
||||
swp-drawer-divider {
|
||||
|
|
@ -260,6 +263,9 @@ swp-toggle-switch input:checked + swp-toggle-track::before {
|
|||
DRAWER FOOTER
|
||||
=========================================== */
|
||||
swp-drawer-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-3);
|
||||
padding: var(--spacing-4) var(--spacing-5);
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@
|
|||
* Employees-specific styling only.
|
||||
* Reuses: swp-stat-card (stats.css), swp-stats-row (stats.css), swp-tab-bar (tabs.css),
|
||||
* swp-btn, swp-status-badge, swp-icon-btn, swp-card, swp-section-label,
|
||||
* swp-add-button (components.css),
|
||||
* swp-add-button, swp-tags-row, swp-tag, swp-status-indicator,
|
||||
* swp-fact-boxes-inline, swp-fact-inline, swp-edit-section/row/label/value/select,
|
||||
* swp-detail-grid, swp-back-link (components.css),
|
||||
* swp-row-toggle (cash.css),
|
||||
* swp-sticky-header, swp-header-content (page.css),
|
||||
* swp-toggle-slider, swp-checkbox-list (controls.css)
|
||||
*
|
||||
* Creates: swp-employee-table, swp-employee-row, swp-user-info,
|
||||
* swp-employee-avatar-large, swp-employee-detail-header,
|
||||
* swp-fact-inline, swp-edit-section/row/label/value/select, swp-detail-grid,
|
||||
* swp-employee-avatar-large, swp-employee-detail-header, swp-employee-status,
|
||||
* swp-salary-table, swp-document-list/item/info/name/meta/actions,
|
||||
* swp-subsection/title, swp-simple-list/item/text
|
||||
*/
|
||||
|
|
@ -262,51 +263,7 @@ swp-employee-name {
|
|||
}
|
||||
|
||||
/* ===========================================
|
||||
TAGS
|
||||
=========================================== */
|
||||
swp-tags-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
swp-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-1);
|
||||
padding: var(--spacing-1) var(--spacing-3);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-background);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
&.master {
|
||||
background: var(--bg-purple-strong);
|
||||
color: var(--color-purple);
|
||||
}
|
||||
|
||||
&.senior {
|
||||
background: var(--bg-blue-strong);
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
&.junior {
|
||||
background: var(--bg-amber-strong);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
&.cert {
|
||||
background: var(--bg-teal-strong);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
EMPLOYEE STATUS INDICATOR
|
||||
EMPLOYEE STATUS (alias for swp-status-indicator)
|
||||
=========================================== */
|
||||
swp-employee-status {
|
||||
display: inline-flex;
|
||||
|
|
@ -338,131 +295,7 @@ swp-employee-status {
|
|||
}
|
||||
|
||||
/* ===========================================
|
||||
FACT BOXES (Inline)
|
||||
=========================================== */
|
||||
swp-fact-boxes-inline {
|
||||
display: flex;
|
||||
gap: var(--spacing-12);
|
||||
margin-top: var(--spacing-1);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
swp-fact-inline {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-2);
|
||||
|
||||
swp-fact-inline-value {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
swp-fact-inline-label {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
EDIT SECTION (Grid + Subgrid)
|
||||
=========================================== */
|
||||
swp-edit-section {
|
||||
display: grid;
|
||||
grid-template-columns: 140px 1fr;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
swp-edit-row {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
font-size: var(--font-size-base);
|
||||
padding: var(--spacing-4) var(--spacing-5);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-background-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
transition: all var(--transition-fast);
|
||||
cursor: text;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: var(--color-surface);
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
|
||||
&[data-type="number"] {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
width: 150px;
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
swp-edit-label {
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
swp-edit-value {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
padding: var(--spacing-4) var(--spacing-5);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-background-alt);
|
||||
border: 1px solid transparent;
|
||||
transition: all var(--transition-fast);
|
||||
cursor: text;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: var(--color-surface);
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
|
||||
&.mono {
|
||||
font-family: var(--font-mono);
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
swp-edit-select {
|
||||
display: block;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: var(--spacing-2) var(--spacing-4);
|
||||
font-size: var(--font-size-base);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
VIEW CONTAINERS (List/Detail swap)
|
||||
VIEW CONTAINERS (Employee-specific)
|
||||
=========================================== */
|
||||
swp-employees-list-view,
|
||||
swp-employee-detail-view {
|
||||
|
|
@ -478,48 +311,6 @@ swp-employee-detail-view {
|
|||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
/* View transition states */
|
||||
.view-fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.view-fade-in {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
swp-back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-sm);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
DETAIL GRID (2-column layout)
|
||||
=========================================== */
|
||||
swp-detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--spacing-8);
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
SCHEDULE GRID (Hours tab)
|
||||
=========================================== */
|
||||
|
|
@ -756,78 +547,6 @@ swp-simple-item {
|
|||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
RATES DRAWER CONTENT
|
||||
=========================================== */
|
||||
.rates-content {
|
||||
swp-data-table {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 1fr 100px;
|
||||
}
|
||||
|
||||
swp-data-row {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
swp-data-label {
|
||||
font-size: var(--font-size-base);
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
swp-data-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-self: end;
|
||||
gap: 4px;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
input {
|
||||
width: 100px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.disabled input {
|
||||
opacity: 0.4;
|
||||
background: var(--color-background);
|
||||
}
|
||||
}
|
||||
|
||||
swp-section-label {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
swp-data-section {
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
FOCUS HIGHLIGHT (double-click to edit)
|
||||
=========================================== */
|
||||
|
|
@ -917,6 +636,7 @@ swp-schedule-table {
|
|||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
|
||||
}
|
||||
|
||||
swp-schedule-cell {
|
||||
|
|
@ -1263,143 +983,6 @@ swp-employee-display {
|
|||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
SCHEDULE DRAWER (matches POC exactly)
|
||||
=========================================== */
|
||||
|
||||
/* Drawer header with background */
|
||||
#schedule-drawer swp-drawer-header {
|
||||
background: var(--color-background-alt);
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
#schedule-drawer swp-drawer-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Drawer body/content */
|
||||
#schedule-drawer swp-drawer-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Form row layout */
|
||||
#schedule-drawer swp-form-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Form labels - uppercase style from POC */
|
||||
#schedule-drawer swp-form-label {
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
.optional,
|
||||
.auto {
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* Form value (read-only display) */
|
||||
#schedule-drawer swp-form-value {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* Form divider */
|
||||
#schedule-drawer swp-form-divider {
|
||||
display: block;
|
||||
height: 1px;
|
||||
background: var(--color-border);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* Form hint text */
|
||||
#schedule-drawer swp-form-hint {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-muted);
|
||||
margin: -8px 0 16px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Form group - gray card background from POC */
|
||||
#schedule-drawer swp-form-group {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
background: var(--color-background-alt);
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
|
||||
swp-form-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form select wrapper */
|
||||
#schedule-drawer swp-form-select {
|
||||
display: block;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Text inputs in drawer */
|
||||
#schedule-drawer input[type="text"],
|
||||
#schedule-drawer input[type="date"] {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
/* Drawer footer with background */
|
||||
#schedule-drawer swp-drawer-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
background: var(--color-background-alt);
|
||||
|
||||
swp-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
RESPONSIVE
|
||||
=========================================== */
|
||||
|
|
@ -1409,11 +992,6 @@ swp-employee-display {
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
swp-detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
swp-employee-table {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
/**
|
||||
* Services List Styles
|
||||
* Services Styles
|
||||
*
|
||||
* Feature-specific styling only.
|
||||
* Reuses: swp-stat-card (stats.css), swp-data-table (components.css),
|
||||
* swp-sticky-header (page.css), swp-row-toggle (employees.css),
|
||||
* swp-btn (components.css)
|
||||
* Reuses:
|
||||
* - swp-stat-card (stats.css)
|
||||
* - swp-data-table (components.css)
|
||||
* - swp-sticky-header, swp-page-container (page.css)
|
||||
* - swp-row-toggle (components.css)
|
||||
* - swp-btn (components.css)
|
||||
* - swp-detail-grid, swp-edit-section (components.css)
|
||||
* - swp-tags-row, swp-tag (components.css)
|
||||
* - swp-status-indicator (components.css)
|
||||
* - swp-fact-boxes-inline (components.css)
|
||||
* - view-fade-out (components.css)
|
||||
*/
|
||||
|
||||
/* ===========================================
|
||||
|
|
@ -18,6 +26,11 @@ swp-services-header {
|
|||
margin-bottom: var(--section-gap);
|
||||
}
|
||||
|
||||
swp-services-header swp-btn-group {
|
||||
display: flex;
|
||||
gap: var(--spacing-3);
|
||||
}
|
||||
|
||||
swp-search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -170,6 +183,29 @@ swp-category-row[data-expanded="false"] swp-category-toggle i {
|
|||
margin-left: var(--spacing-2);
|
||||
}
|
||||
|
||||
/* Category edit icon */
|
||||
swp-category-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: var(--color-text-tertiary);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
swp-category-row:hover swp-category-edit {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
swp-category-edit:hover {
|
||||
color: var(--color-blue);
|
||||
background: var(--bg-blue-medium);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
SERVICE ROW (indented under category)
|
||||
=========================================== */
|
||||
|
|
@ -191,52 +227,48 @@ swp-card.services-list swp-data-table-row:hover swp-row-toggle {
|
|||
}
|
||||
|
||||
/* ===========================================
|
||||
CATEGORIES LIST TABLE
|
||||
VIEW CONTAINERS (List / Detail)
|
||||
=========================================== */
|
||||
|
||||
swp-card.categories-list {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
swp-services-list-view,
|
||||
swp-service-detail-view {
|
||||
transition: opacity 100ms ease;
|
||||
}
|
||||
|
||||
/* Table columns: Category(1fr) | ServiceCount(120px) | Caret(40px) */
|
||||
swp-card.categories-list swp-data-table {
|
||||
grid-template-columns: 1fr 120px 40px;
|
||||
swp-service-detail-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
swp-card.categories-list swp-data-table-header,
|
||||
swp-card.categories-list swp-data-table-row {
|
||||
padding: 0 var(--spacing-10);
|
||||
/* ===========================================
|
||||
SERVICE DETAIL HEADER
|
||||
=========================================== */
|
||||
|
||||
swp-service-detail-header {
|
||||
display: flex;
|
||||
gap: var(--spacing-6);
|
||||
padding: var(--spacing-6) 0;
|
||||
}
|
||||
|
||||
swp-card.categories-list swp-data-table-header swp-data-table-cell {
|
||||
padding-top: var(--spacing-5);
|
||||
padding-bottom: var(--spacing-5);
|
||||
swp-service-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
swp-card.categories-list swp-data-table-row {
|
||||
cursor: pointer;
|
||||
swp-service-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
swp-card.categories-list swp-data-table-cell {
|
||||
padding: var(--spacing-5) 0;
|
||||
swp-service-name {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text);
|
||||
outline: none;
|
||||
|
||||
&:last-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:focus {
|
||||
border-bottom: 1px dashed var(--color-teal);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mono font for service count */
|
||||
swp-card.categories-list swp-data-table-row swp-data-table-cell:nth-child(2) {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
swp-card.categories-list swp-data-table-row:hover {
|
||||
background: var(--color-background-hover);
|
||||
}
|
||||
|
||||
swp-card.categories-list swp-data-table-row:hover swp-row-toggle {
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* Handles generic UI controls functionality:
|
||||
* - Toggle sliders (Ja/Nej switches)
|
||||
* - Select dropdowns (Popover API)
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -11,6 +12,7 @@
|
|||
export class ControlsController {
|
||||
constructor() {
|
||||
this.initToggleSliders();
|
||||
this.initSelectDropdowns();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -33,4 +35,54 @@ export class ControlsController {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all select dropdowns on the page
|
||||
* Uses Popover API for dropdown behavior
|
||||
*/
|
||||
private initSelectDropdowns(): void {
|
||||
document.querySelectorAll('swp-select').forEach(select => {
|
||||
const trigger = select.querySelector('button');
|
||||
const popover = select.querySelector('[popover]') as HTMLElement | null;
|
||||
const options = select.querySelectorAll('swp-select-option');
|
||||
|
||||
if (!trigger || !popover) return;
|
||||
|
||||
// Update aria-expanded on toggle
|
||||
popover.addEventListener('toggle', (e: Event) => {
|
||||
const event = e as ToggleEvent;
|
||||
trigger.setAttribute('aria-expanded', event.newState === 'open' ? 'true' : 'false');
|
||||
});
|
||||
|
||||
// Handle option selection
|
||||
options.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
const value = (option as HTMLElement).dataset.value;
|
||||
const label = option.textContent?.trim() || '';
|
||||
|
||||
// Update selected state
|
||||
options.forEach(o => o.classList.remove('selected'));
|
||||
option.classList.add('selected');
|
||||
|
||||
// Update trigger display
|
||||
const valueEl = trigger.querySelector('swp-select-value');
|
||||
if (valueEl) {
|
||||
valueEl.textContent = label;
|
||||
}
|
||||
|
||||
// Update data-value on select element
|
||||
(select as HTMLElement).dataset.value = value;
|
||||
|
||||
// Close popover
|
||||
popover.hidePopover();
|
||||
|
||||
// Dispatch custom event
|
||||
select.dispatchEvent(new CustomEvent('change', {
|
||||
bubbles: true,
|
||||
detail: { value, label }
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
/**
|
||||
* Services Controller
|
||||
* Handles category collapse/expand animations and fuzzy search
|
||||
*
|
||||
* Handles:
|
||||
* - Category collapse/expand animations
|
||||
* - Fuzzy search
|
||||
* - Content swap between list view and detail view
|
||||
* - Tab switching within detail view
|
||||
* - History API for browser back/forward navigation
|
||||
*/
|
||||
|
||||
import Fuse from 'fuse.js';
|
||||
|
|
@ -15,14 +21,27 @@ interface ServiceItem {
|
|||
export class ServicesController {
|
||||
private fuse: Fuse<ServiceItem> | null = null;
|
||||
private services: ServiceItem[] = [];
|
||||
private listView: HTMLElement | null = null;
|
||||
private detailView: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
this.listView = document.getElementById('services-list-view');
|
||||
this.detailView = document.getElementById('service-detail-view');
|
||||
|
||||
// Only initialize if we're on the services page
|
||||
if (!this.listView) return;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
this.setupCategoryToggle();
|
||||
this.setupSearch();
|
||||
this.setupDetailTabs();
|
||||
this.setupChevronNavigation();
|
||||
this.setupBackNavigation();
|
||||
this.setupHistoryNavigation();
|
||||
this.restoreStateFromUrl();
|
||||
}
|
||||
|
||||
private setupSearch(): void {
|
||||
|
|
@ -225,4 +244,180 @@ export class ServicesController {
|
|||
});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup popstate listener for browser back/forward
|
||||
*/
|
||||
private setupHistoryNavigation(): void {
|
||||
window.addEventListener('popstate', (e: PopStateEvent) => {
|
||||
if (e.state?.serviceKey) {
|
||||
this.showDetailViewInternal(e.state.serviceKey);
|
||||
} else {
|
||||
this.showListViewInternal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore view state from URL on page load
|
||||
*/
|
||||
private restoreStateFromUrl(): void {
|
||||
const hash = window.location.hash;
|
||||
if (hash.startsWith('#service-')) {
|
||||
const serviceKey = hash.substring(1); // Remove #
|
||||
this.showDetailViewInternal(serviceKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup tab switching for the detail view
|
||||
*/
|
||||
private setupDetailTabs(): void {
|
||||
if (!this.detailView) return;
|
||||
|
||||
const tabs = this.detailView.querySelectorAll<HTMLElement>('swp-tab-bar > swp-tab[data-tab]');
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const targetTab = tab.dataset.tab;
|
||||
if (targetTab) {
|
||||
this.switchTab(this.detailView!, targetTab);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a specific tab within a container
|
||||
*/
|
||||
private switchTab(container: HTMLElement, targetTab: string): void {
|
||||
const tabs = container.querySelectorAll<HTMLElement>('swp-tab-bar > swp-tab[data-tab]');
|
||||
const contents = container.querySelectorAll<HTMLElement>('swp-tab-content[data-tab]');
|
||||
|
||||
tabs.forEach(t => {
|
||||
t.classList.toggle('active', t.dataset.tab === targetTab);
|
||||
});
|
||||
|
||||
contents.forEach(content => {
|
||||
content.classList.toggle('active', content.dataset.tab === targetTab);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup row click to show detail view
|
||||
* Ignores clicks on category rows
|
||||
*/
|
||||
private setupChevronNavigation(): void {
|
||||
document.addEventListener('click', (e: Event) => {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// Ignore clicks on category rows
|
||||
if (target.closest('swp-category-row')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row = target.closest<HTMLElement>('swp-data-table-row[data-service-detail]');
|
||||
|
||||
if (row) {
|
||||
const serviceKey = row.dataset.serviceDetail;
|
||||
if (serviceKey) {
|
||||
this.showDetailView(serviceKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup back button to return to list view
|
||||
*/
|
||||
private setupBackNavigation(): void {
|
||||
document.addEventListener('click', (e: Event) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const backLink = target.closest<HTMLElement>('[data-service-back]');
|
||||
|
||||
if (backLink) {
|
||||
this.showListView();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the detail view and hide list view (with history push)
|
||||
*/
|
||||
private showDetailView(serviceKey: string): void {
|
||||
// Push state to history
|
||||
history.pushState(
|
||||
{ serviceKey },
|
||||
'',
|
||||
`#${serviceKey}`
|
||||
);
|
||||
this.showDetailViewInternal(serviceKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show detail view without modifying history (for popstate)
|
||||
*/
|
||||
private showDetailViewInternal(serviceKey: string): void {
|
||||
if (this.listView && this.detailView) {
|
||||
// Fade out list view
|
||||
this.listView.classList.add('view-fade-out');
|
||||
|
||||
// After fade, switch views
|
||||
setTimeout(() => {
|
||||
this.listView!.style.display = 'none';
|
||||
this.listView!.classList.remove('view-fade-out');
|
||||
|
||||
// Show detail view with fade in
|
||||
this.detailView!.style.display = 'block';
|
||||
this.detailView!.classList.add('view-fade-out');
|
||||
this.detailView!.dataset.service = serviceKey;
|
||||
|
||||
// Reset to first tab
|
||||
this.switchTab(this.detailView!, 'general');
|
||||
|
||||
// Trigger fade in
|
||||
requestAnimationFrame(() => {
|
||||
this.detailView!.classList.remove('view-fade-out');
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the list view and hide detail view (with history push)
|
||||
*/
|
||||
private showListView(): void {
|
||||
// Push state to history (clear hash)
|
||||
history.pushState(
|
||||
{},
|
||||
'',
|
||||
window.location.pathname
|
||||
);
|
||||
this.showListViewInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show list view without modifying history (for popstate)
|
||||
*/
|
||||
private showListViewInternal(): void {
|
||||
if (this.listView && this.detailView) {
|
||||
// Fade out detail view
|
||||
this.detailView.classList.add('view-fade-out');
|
||||
|
||||
// After fade, switch views
|
||||
setTimeout(() => {
|
||||
this.detailView!.style.display = 'none';
|
||||
this.detailView!.classList.remove('view-fade-out');
|
||||
|
||||
// Show list view with fade in
|
||||
this.listView!.style.display = 'block';
|
||||
this.listView!.classList.add('view-fade-out');
|
||||
|
||||
// Trigger fade in
|
||||
requestAnimationFrame(() => {
|
||||
this.listView!.classList.remove('view-fade-out');
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue