import { DeesElement, property, html, customElement, type TemplateResult, cssManager, css, unsafeCSS, } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; import type { IServiceStatus } from '../interfaces/index.js'; import { fonts, colors, shadows, borderRadius, spacing, commonStyles, getStatusColor } from '../styles/shared.styles.js'; import './internal/uplinternal-miniheading.js'; import { demoFunc } from './upl-statuspage-assetsselector.demo.js'; declare global { interface HTMLElementTagNameMap { 'upl-statuspage-assetsselector': UplStatuspageAssetsselector; } } @customElement('upl-statuspage-assetsselector') export class UplStatuspageAssetsselector extends DeesElement { public static demo = demoFunc; @property({ type: Array }) public services: IServiceStatus[] = []; @property({ type: String }) public filterText: string = ''; @property({ type: String }) public filterCategory: string = 'all'; @property({ type: Boolean }) public showOnlySelected: boolean = false; @property({ type: Boolean }) public loading: boolean = false; @property({ type: Boolean }) public expanded: boolean = false; constructor() { super(); } public static styles = [ cssManager.defaultStyles, commonStyles, css` :host { display: block; background: transparent; font-family: ${unsafeCSS(fonts.base)}; color: ${colors.text.primary}; } .container { max-width: 1200px; margin: 0 auto; padding: 0 ${unsafeCSS(spacing.lg)} ${unsafeCSS(spacing.lg)} ${unsafeCSS(spacing.lg)}; } .controls { display: flex; gap: ${unsafeCSS(spacing.sm)}; margin-bottom: ${unsafeCSS(spacing.md)}; flex-wrap: wrap; align-items: center; } .search-input { flex: 1; min-width: 200px; padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.sm)}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-radius: ${unsafeCSS(borderRadius.base)}; background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; font-size: 13px; font-family: ${unsafeCSS(fonts.base)}; transition: all 0.2s ease; height: 32px; } .search-input:focus { outline: none; border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; } .search-input::placeholder { color: ${cssManager.bdTheme('#9ca3af', '#71717a')}; } .filter-button { display: inline-flex; align-items: center; justify-content: center; padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.sm)}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-radius: ${unsafeCSS(borderRadius.base)}; background: transparent; color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')}; cursor: pointer; font-size: 13px; font-weight: 400; font-family: ${unsafeCSS(fonts.base)}; transition: all 0.2s ease; height: 32px; } .filter-button:hover { border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; } .filter-button.active { background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; color: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; } .selected-services { display: flex; flex-wrap: wrap; gap: ${unsafeCSS(spacing.sm)}; align-items: center; } .service-pill { display: inline-flex; align-items: center; gap: ${unsafeCSS(spacing.xs)}; padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.md)}; background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-radius: ${unsafeCSS(borderRadius.full)}; font-size: 13px; transition: all 0.2s ease; } .service-pill:hover { border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; } .service-pill .status-dot { width: 6px; height: 6px; border-radius: 50%; } .manage-button { display: inline-flex; align-items: center; gap: ${unsafeCSS(spacing.xs)}; padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.md)}; background: transparent; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-radius: ${unsafeCSS(borderRadius.base)}; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')}; font-family: ${unsafeCSS(fonts.base)}; } .manage-button:hover { border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; } .expandable-section { margin-top: ${unsafeCSS(spacing.lg)}; overflow: hidden; transition: all 0.3s ease; } .expandable-content { padding: ${unsafeCSS(spacing.lg)}; background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-radius: ${unsafeCSS(borderRadius.base)}; } .assets-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: ${unsafeCSS(spacing.sm)}; margin-top: ${unsafeCSS(spacing.md)}; } .asset-card { display: flex; align-items: center; padding: ${unsafeCSS(spacing.sm)} ${unsafeCSS(spacing.md)}; background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; border-radius: ${unsafeCSS(borderRadius.base)}; cursor: pointer; transition: all 0.2s ease; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; gap: ${unsafeCSS(spacing.sm)}; } .asset-card:hover { border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')}; } .asset-card.selected { border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; background: ${cssManager.bdTheme('#f9fafb', '#0f0f0f')}; } .asset-checkbox { width: 16px; height: 16px; cursor: pointer; accent-color: ${colors.text.primary}; flex-shrink: 0; } .asset-info { flex: 1; min-width: 0; } .asset-name { font-weight: 600; font-size: 14px; margin-bottom: ${unsafeCSS(spacing.xs)}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .asset-description { font-size: 13px; color: ${colors.text.secondary}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .asset-status { display: flex; align-items: center; gap: ${unsafeCSS(spacing.xs)}; flex-shrink: 0; } .status-indicator { width: 8px; height: 8px; border-radius: ${unsafeCSS(borderRadius.full)}; } .status-indicator.operational, .status-dot.operational { background: #22c55e; } .status-indicator.degraded, .status-dot.degraded { background: #fbbf24; } .status-indicator.partial_outage, .status-dot.partial_outage { background: #f87171; } .status-indicator.major_outage, .status-dot.major_outage { background: #ef4444; } .status-indicator.maintenance, .status-dot.maintenance { background: #60a5fa; } .status-text { font-size: 12px; text-transform: capitalize; color: ${colors.text.secondary}; } .loading-message, .no-results { grid-column: 1 / -1; text-align: center; padding: ${unsafeCSS(spacing.xl)}; color: ${cssManager.bdTheme('#9ca3af', '#71717a')}; font-size: 13px; } .summary { text-align: right; font-size: 12px; margin-top: ${unsafeCSS(spacing.md)}; color: ${cssManager.bdTheme('#9ca3af', '#71717a')}; } .no-services { padding: ${unsafeCSS(spacing.xl)}; text-align: center; color: ${cssManager.bdTheme('#9ca3af', '#71717a')}; font-size: 13px; } @media (max-width: 640px) { .container { padding: 0 ${unsafeCSS(spacing.md)} ${unsafeCSS(spacing.md)} ${unsafeCSS(spacing.md)}; } .controls { flex-direction: column; align-items: stretch; } .search-input { width: 100%; } .selected-services { flex-direction: column; align-items: stretch; } .service-pill { width: auto; } .expandable-content { padding: ${unsafeCSS(spacing.md)}; } .assets-grid { grid-template-columns: 1fr; } .asset-card { padding: ${unsafeCSS(spacing.sm)}; } } `, ] public render(): TemplateResult { const selectedServices = this.services.filter(s => s.selected); const selectedCount = selectedServices.length; const categories = this.getUniqueCategories(); return html`
Monitored Assets
${selectedCount === 0 ? html` No services selected ` : selectedCount > 5 && !this.expanded ? html` ${selectedServices.slice(0, 4).map(service => html`
${service.displayName}
`)} +${selectedCount - 4} more ` : selectedServices.map(service => html`
${service.displayName}
`)}
${this.expanded ? html`
{ this.filterText = (e.target as HTMLInputElement).value; }} /> ${categories.map(category => html` `)}
${this.loading ? html`
Loading services...
` : this.renderServiceGrid()}
${selectedCount} of ${this.services.length} services selected
` : ''}
`; } private getFilteredServices(): IServiceStatus[] { return this.services.filter(service => { // Apply text filter if (this.filterText && !service.displayName.toLowerCase().includes(this.filterText.toLowerCase()) && (!service.description || !service.description.toLowerCase().includes(this.filterText.toLowerCase()))) { return false; } // Apply category filter if (this.filterCategory !== 'all' && service.category !== this.filterCategory) { return false; } // Apply selected filter if (this.showOnlySelected && !service.selected) { return false; } return true; }); } private getUniqueCategories(): string[] { const categories = new Set(); this.services.forEach(service => { if (service.category) { categories.add(service.category); } }); return Array.from(categories).sort(); } private toggleService(serviceId: string) { const service = this.services.find(s => s.id === serviceId); if (service) { service.selected = !service.selected; this.requestUpdate(); this.dispatchEvent(new CustomEvent('selectionChanged', { detail: { serviceId, selected: service.selected, selectedServices: this.services.filter(s => s.selected).map(s => s.id) }, bubbles: true, composed: true })); } } private selectAll() { const filteredServices = this.getFilteredServices(); filteredServices.forEach(service => { service.selected = true; }); this.requestUpdate(); this.emitSelectionUpdate(); } private selectNone() { const filteredServices = this.getFilteredServices(); filteredServices.forEach(service => { service.selected = false; }); this.requestUpdate(); this.emitSelectionUpdate(); } private emitSelectionUpdate() { this.dispatchEvent(new CustomEvent('selectionChanged', { detail: { selectedServices: this.services.filter(s => s.selected).map(s => s.id) }, bubbles: true, composed: true })); } private renderServiceGrid(): TemplateResult { const filteredServices = this.getFilteredServices(); if (filteredServices.length === 0) { return html`
No services found matching your criteria
`; } return html`${filteredServices.map(service => html`
this.toggleService(service.id)} > e.stopPropagation()} @change=${(e: Event) => { e.stopPropagation(); this.toggleService(service.id); }} />
${service.displayName}
${service.description ? html`
${service.description}
` : ''}
${service.currentStatus.replace(/_/g, ' ')}
`)}`; } }