import * as plugins from '../../plugins.js'; import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager, unsafeCSS, state, } from '@design.estate/dees-element'; import * as sharedStyles from '../../styles/shared.styles.js'; import type { IIncidentUpdateFormData, IIncidentDetails } from '../../interfaces/index.js'; import { demoFunc } from './upladmin-incident-update.demo.js'; declare global { interface HTMLElementTagNameMap { 'upladmin-incident-update': UpladminIncidentUpdate; } } type TIncidentStatus = 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem'; @customElement('upladmin-incident-update') export class UpladminIncidentUpdate extends DeesElement { public static demo = demoFunc; @property({ type: Object }) accessor incident: IIncidentDetails | null = null; @property({ type: Boolean }) accessor loading: boolean = false; @state() accessor formData: IIncidentUpdateFormData = { status: 'investigating', message: '', author: '', }; @state() accessor errors: Record = {}; private statusIcons: Record = { investigating: 'lucide:Search', identified: 'lucide:Target', monitoring: 'lucide:Eye', resolved: 'lucide:CheckCircle', postmortem: 'lucide:FileText', }; public static styles = [ plugins.domtools.elementBasic.staticStyles, sharedStyles.commonStyles, css` :host { display: block; font-family: ${unsafeCSS(sharedStyles.fonts.base)}; } .update-container { background: ${sharedStyles.colors.background.secondary}; border: 1px solid ${sharedStyles.colors.border.default}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)}; overflow: hidden; } .update-header { padding: ${unsafeCSS(sharedStyles.spacing.lg)}; border-bottom: 1px solid ${sharedStyles.colors.border.default}; background: ${sharedStyles.colors.background.muted}; } .update-title-row { display: flex; align-items: center; gap: ${unsafeCSS(sharedStyles.spacing.md)}; margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)}; } .update-title-row dees-icon { --icon-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; } .update-title { font-size: 18px; font-weight: 600; color: ${sharedStyles.colors.text.primary}; margin: 0; } .incident-info { display: flex; align-items: center; gap: ${unsafeCSS(sharedStyles.spacing.sm)}; padding-left: 36px; } .incident-name { font-size: 14px; color: ${sharedStyles.colors.text.secondary}; } .severity-badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; font-size: 11px; font-weight: 500; border-radius: 9999px; text-transform: uppercase; } .severity-badge dees-icon { font-size: 12px; } .severity-badge.critical { background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.2)')}; color: ${sharedStyles.colors.status.majorOutage}; --icon-color: ${sharedStyles.colors.status.majorOutage}; } .severity-badge.major { background: ${cssManager.bdTheme('rgba(249, 115, 22, 0.1)', 'rgba(249, 115, 22, 0.2)')}; color: ${sharedStyles.colors.status.partialOutage}; --icon-color: ${sharedStyles.colors.status.partialOutage}; } .severity-badge.minor { background: ${cssManager.bdTheme('rgba(234, 179, 8, 0.1)', 'rgba(234, 179, 8, 0.2)')}; color: ${sharedStyles.colors.status.degraded}; --icon-color: ${sharedStyles.colors.status.degraded}; } .severity-badge.maintenance { background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.2)')}; color: ${sharedStyles.colors.status.maintenance}; --icon-color: ${sharedStyles.colors.status.maintenance}; } .update-body { display: grid; gap: ${unsafeCSS(sharedStyles.spacing.lg)}; padding: ${unsafeCSS(sharedStyles.spacing.lg)}; } dees-form { display: contents; } .status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: ${unsafeCSS(sharedStyles.spacing.sm)}; } .status-option { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 18px 14px; background: ${sharedStyles.colors.background.primary}; border: 2px solid ${sharedStyles.colors.border.default}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)}; cursor: pointer; transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; text-align: center; } .status-option:hover { border-color: ${sharedStyles.colors.border.strong}; background: ${sharedStyles.colors.background.muted}; } .status-option.selected { border-color: ${sharedStyles.colors.accent.primary}; background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')}; } .status-option input { display: none; } .status-option.investigating dees-icon { --icon-color: ${sharedStyles.colors.status.partialOutage}; } .status-option.identified dees-icon { --icon-color: ${sharedStyles.colors.status.degraded}; } .status-option.monitoring dees-icon { --icon-color: ${sharedStyles.colors.status.maintenance}; } .status-option.resolved dees-icon { --icon-color: ${sharedStyles.colors.status.operational}; } .status-option.postmortem dees-icon { --icon-color: #a855f7; } .status-label { font-size: 13px; font-weight: 600; color: ${sharedStyles.colors.text.primary}; } .status-desc { font-size: 11px; color: ${sharedStyles.colors.text.muted}; line-height: 1.3; } .field-label { display: block; font-size: 13px; font-weight: 500; color: ${sharedStyles.colors.text.primary}; margin-bottom: ${unsafeCSS(sharedStyles.spacing.xs)}; } .field-label.required::after { content: ' *'; color: ${sharedStyles.colors.accent.danger}; } .template-section { margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)}; } .template-label { font-size: 12px; color: ${sharedStyles.colors.text.muted}; margin-bottom: 8px; } .template-buttons { display: flex; flex-wrap: wrap; gap: 8px; } .template-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; font-size: 12px; font-weight: 500; font-family: ${unsafeCSS(sharedStyles.fonts.base)}; color: ${sharedStyles.colors.text.secondary}; background: ${sharedStyles.colors.background.primary}; border: 1px solid ${sharedStyles.colors.border.default}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)}; cursor: pointer; transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; } .template-btn:hover { background: ${sharedStyles.colors.background.muted}; border-color: ${sharedStyles.colors.border.strong}; color: ${sharedStyles.colors.text.primary}; } .template-btn dees-icon { --icon-color: currentColor; opacity: 0.6; } .update-actions { display: flex; justify-content: flex-end; gap: ${unsafeCSS(sharedStyles.spacing.sm)}; padding: ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.lg)}; border-top: 1px solid ${sharedStyles.colors.border.default}; background: ${sharedStyles.colors.background.muted}; } /* Style dees-input components */ dees-input-text { --dees-input-background: ${sharedStyles.colors.background.primary}; --dees-input-border-color: ${sharedStyles.colors.border.default}; } ` ]; async connectedCallback() { await super.connectedCallback(); if (this.incident) { this.formData = { ...this.formData, status: this.incident.status, }; } } updated(changedProperties: Map) { if (changedProperties.has('incident') && this.incident) { this.formData = { ...this.formData, status: this.incident.status, }; } } public render(): TemplateResult { if (!this.incident) { return html`
No incident selected
`; } const statusOptions: Array<{ value: TIncidentStatus; label: string; desc: string }> = [ { value: 'investigating', label: 'Investigating', desc: 'Looking into the issue' }, { value: 'identified', label: 'Identified', desc: 'Root cause found' }, { value: 'monitoring', label: 'Monitoring', desc: 'Fix applied, watching' }, { value: 'resolved', label: 'Resolved', desc: 'Issue is fixed' }, { value: 'postmortem', label: 'Postmortem', desc: 'Analysis complete' }, ]; const templates: Array<{ icon: string; label: string; status: TIncidentStatus; message: string }> = [ { icon: 'lucide:Search', label: 'Started investigating', status: 'investigating', message: 'We are currently investigating this issue.' }, { icon: 'lucide:Target', label: 'Issue identified', status: 'identified', message: 'We have identified the root cause and are working on a fix.' }, { icon: 'lucide:Rocket', label: 'Fix deployed', status: 'monitoring', message: 'A fix has been deployed. We are monitoring the results.' }, { icon: 'lucide:CheckCircle', label: 'Resolved', status: 'resolved', message: 'This incident has been resolved. All systems are operating normally.' }, { icon: 'lucide:FileText', label: 'Postmortem', status: 'postmortem', message: 'Postmortem will be released shortly.' }, ]; const severityIcons: Record = { critical: 'lucide:AlertCircle', major: 'lucide:AlertTriangle', minor: 'lucide:Info', maintenance: 'lucide:Wrench', }; return html`

Post Update

${this.incident.severity} ${this.incident.title}
Select a template to prefill status and message:
${templates.map(tpl => html` `)}
${statusOptions.map(opt => html` `)}
Cancel ${this.formData.status === 'resolved' ? html` ${this.loading ? html`` : html``} Resolve Incident ` : html` ${this.loading ? html`` : html``} Post Update `}
`; } private handleMessageChange(e: CustomEvent) { this.formData = { ...this.formData, message: e.detail }; if (this.errors.message) { this.errors = { ...this.errors, message: '' }; } } private handleAuthorChange(e: CustomEvent) { this.formData = { ...this.formData, author: e.detail }; } private handleStatusChange(status: TIncidentStatus) { this.formData = { ...this.formData, status }; } private applyTemplate(template: { status: TIncidentStatus; message: string }) { this.formData = { ...this.formData, status: template.status, message: template.message }; } private validate(): boolean { const errors: Record = {}; if (!this.formData.message?.trim()) { errors.message = 'Update message is required'; } this.errors = errors; return Object.keys(errors).length === 0; } private handlePost() { if (!this.validate()) { return; } this.dispatchEvent(new CustomEvent('updatePost', { detail: { incidentId: this.incident?.id, update: { ...this.formData } }, bubbles: true, composed: true })); } private handleCancel() { this.dispatchEvent(new CustomEvent('updateCancel', { bubbles: true, composed: true })); } public reset() { this.formData = { status: this.incident?.status || 'investigating', message: '', author: '', }; this.errors = {}; } }