/** * @file sdig-contract-header.ts * @description Contract header component with title, status, and quick actions */ import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager, state, } from '@design.estate/dees-element'; import * as plugins from '../../plugins.js'; declare global { interface HTMLElementTagNameMap { 'sdig-contract-header': SdigContractHeader; } } @customElement('sdig-contract-header') export class SdigContractHeader extends DeesElement { // ============================================================================ // STATIC // ============================================================================ public static demo = () => html` `; public static styles = [ cssManager.defaultStyles, css` :host { display: block; } .header-card { background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')}; border-radius: 12px; padding: 24px; } .header-top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 20px; } .title-section { flex: 1; } .contract-number { font-size: 13px; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; margin-bottom: 8px; } .title-input-wrapper { position: relative; } .title-input { width: 100%; font-size: 24px; font-weight: 700; color: ${cssManager.bdTheme('#111111', '#fafafa')}; background: transparent; border: none; padding: 0; outline: none; border-bottom: 2px solid transparent; transition: border-color 0.15s ease; } .title-input:focus { border-bottom-color: ${cssManager.bdTheme('#111111', '#fafafa')}; } .title-input::placeholder { color: ${cssManager.bdTheme('#9ca3af', '#6b7280')}; } .status-section { display: flex; align-items: center; gap: 12px; } /* shadcn-style badge */ .status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 2px 10px; border-radius: 6px; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.15s ease; border: 1px solid transparent; line-height: 1.4; } .status-badge:hover:not(:disabled) { filter: brightness(0.95); } .status-badge:disabled { cursor: default; } .status-badge.draft { background: ${cssManager.bdTheme('hsl(48 96% 89%)', 'hsl(48 96% 15%)')}; color: ${cssManager.bdTheme('hsl(25 95% 30%)', 'hsl(48 96% 70%)')}; border-color: ${cssManager.bdTheme('hsl(48 96% 76%)', 'hsl(48 96% 25%)')}; } .status-badge.review { background: ${cssManager.bdTheme('hsl(214 95% 93%)', 'hsl(214 95% 15%)')}; color: ${cssManager.bdTheme('hsl(214 95% 35%)', 'hsl(214 95% 70%)')}; border-color: ${cssManager.bdTheme('hsl(214 95% 80%)', 'hsl(214 95% 25%)')}; } .status-badge.pending { background: ${cssManager.bdTheme('hsl(38 92% 90%)', 'hsl(38 92% 15%)')}; color: ${cssManager.bdTheme('hsl(25 95% 35%)', 'hsl(38 92% 65%)')}; border-color: ${cssManager.bdTheme('hsl(38 92% 75%)', 'hsl(38 92% 25%)')}; } .status-badge.signed, .status-badge.active { background: ${cssManager.bdTheme('hsl(142 76% 90%)', 'hsl(142 76% 15%)')}; color: ${cssManager.bdTheme('hsl(142 76% 28%)', 'hsl(142 76% 65%)')}; border-color: ${cssManager.bdTheme('hsl(142 76% 75%)', 'hsl(142 76% 25%)')}; } .status-badge.terminated { background: ${cssManager.bdTheme('hsl(0 84% 92%)', 'hsl(0 84% 15%)')}; color: ${cssManager.bdTheme('hsl(0 84% 35%)', 'hsl(0 84% 65%)')}; border-color: ${cssManager.bdTheme('hsl(0 84% 80%)', 'hsl(0 84% 25%)')}; } .status-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: 0.8; } /* Meta info row */ .meta-row { display: flex; flex-wrap: wrap; gap: 24px; padding-top: 20px; border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')}; } .meta-item { display: flex; flex-direction: column; gap: 4px; } .meta-label { font-size: 12px; font-weight: 500; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; text-transform: uppercase; letter-spacing: 0.05em; } .meta-value { font-size: 14px; font-weight: 500; color: ${cssManager.bdTheme('#111111', '#fafafa')}; } .meta-value.clickable { cursor: pointer; color: ${cssManager.bdTheme('#2563eb', '#60a5fa')}; } .meta-value.clickable:hover { text-decoration: underline; } /* Tags */ .tags-container { display: flex; flex-wrap: wrap; gap: 6px; } .tag { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 6px; font-size: 12px; font-weight: 500; background: ${cssManager.bdTheme('#f3f4f6', '#27272a')}; color: ${cssManager.bdTheme('#374151', '#d1d5db')}; } /* Quick actions */ .quick-actions { display: flex; gap: 8px; } .action-btn { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 8px; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')}; background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; cursor: pointer; transition: all 0.15s ease; } .action-btn:hover { background: ${cssManager.bdTheme('#f3f4f6', '#18181b')}; color: ${cssManager.bdTheme('#111111', '#fafafa')}; border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')}; } .action-btn dees-icon { font-size: 16px; } /* Status dropdown */ .status-dropdown { position: absolute; top: 100%; right: 0; margin-top: 8px; min-width: 200px; background: ${cssManager.bdTheme('#ffffff', '#18181b')}; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')}; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 100; overflow: hidden; } .status-option { display: flex; align-items: center; gap: 10px; padding: 10px 14px; font-size: 14px; color: ${cssManager.bdTheme('#374151', '#d1d5db')}; cursor: pointer; transition: background 0.1s ease; } .status-option:hover { background: ${cssManager.bdTheme('#f3f4f6', '#27272a')}; } .status-option.selected { background: ${cssManager.bdTheme('#eff6ff', '#1e3a5f')}; color: ${cssManager.bdTheme('#1e40af', '#93c5fd')}; } `, ]; // ============================================================================ // PROPERTIES // ============================================================================ @property({ type: Object }) public accessor contract: plugins.sdInterfaces.IPortableContract | null = null; @property({ type: Boolean }) public accessor readonly: boolean = false; // ============================================================================ // STATE // ============================================================================ @state() private accessor showStatusDropdown: boolean = false; @state() private accessor editingTitle: boolean = false; // ============================================================================ // STATUS CONFIGURATION // ============================================================================ private statusOptions: Array<{ value: plugins.sdInterfaces.TContractStatus; label: string; category: string; }> = [ { value: 'draft', label: 'Draft', category: 'draft' }, { value: 'internal_review', label: 'Internal Review', category: 'review' }, { value: 'legal_review', label: 'Legal Review', category: 'review' }, { value: 'negotiation', label: 'Negotiation', category: 'review' }, { value: 'pending_approval', label: 'Pending Approval', category: 'pending' }, { value: 'pending_signature', label: 'Pending Signature', category: 'pending' }, { value: 'partially_signed', label: 'Partially Signed', category: 'pending' }, { value: 'signed', label: 'Signed', category: 'signed' }, { value: 'executed', label: 'Executed', category: 'signed' }, { value: 'active', label: 'Active', category: 'active' }, { value: 'expired', label: 'Expired', category: 'terminated' }, { value: 'terminated', label: 'Terminated', category: 'terminated' }, { value: 'cancelled', label: 'Cancelled', category: 'terminated' }, { value: 'voided', label: 'Voided', category: 'terminated' }, ]; // ============================================================================ // EVENT HANDLERS // ============================================================================ private handleTitleChange(e: Event) { const input = e.target as HTMLInputElement; this.dispatchEvent( new CustomEvent('field-change', { detail: { path: 'title', value: input.value }, bubbles: true, composed: true, }) ); } private handleStatusChange(status: plugins.sdInterfaces.TContractStatus) { this.showStatusDropdown = false; this.dispatchEvent( new CustomEvent('field-change', { detail: { path: 'lifecycle.currentStatus', value: status }, bubbles: true, composed: true, }) ); } private toggleStatusDropdown() { if (!this.readonly) { this.showStatusDropdown = !this.showStatusDropdown; } } private handleExport() { this.dispatchEvent( new CustomEvent('action', { detail: { action: 'export' }, bubbles: true, composed: true, }) ); } private handleDuplicate() { this.dispatchEvent( new CustomEvent('action', { detail: { action: 'duplicate' }, bubbles: true, composed: true, }) ); } private handleShare() { this.dispatchEvent( new CustomEvent('action', { detail: { action: 'share' }, bubbles: true, composed: true, }) ); } // ============================================================================ // HELPERS // ============================================================================ private getStatusCategory(status: string): string { const option = this.statusOptions.find((o) => o.value === status); return option?.category || 'draft'; } private formatStatus(status: string): string { const option = this.statusOptions.find((o) => o.value === status); return option?.label || status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); } private formatDate(timestamp: number): string { return new Date(timestamp).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', }); } private formatContractType(type: string): string { return type.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); } // ============================================================================ // RENDER // ============================================================================ public render(): TemplateResult { if (!this.contract) { return html`
No contract loaded
`; } const status = this.contract.lifecycle?.currentStatus || 'draft'; const statusCategory = this.getStatusCategory(status); return html`
${this.contract.metadata?.contractNumber ? html`
#${this.contract.metadata.contractNumber}
` : ''}
${this.showStatusDropdown ? html`
${this.statusOptions.map( (option) => html`
this.handleStatusChange(option.value)} > ${option.label}
` )}
` : ''}
${this.contract.metadata ? html`
Type ${this.formatContractType(this.contract.metadata.contractType)}
Category ${this.formatContractType(this.contract.metadata.category)}
Language ${this.contract.metadata.language?.toUpperCase() || 'N/A'}
Jurisdiction ${this.contract.metadata.governingLaw?.country || 'Not specified'} ${this.contract.metadata.governingLaw?.state ? `, ${this.contract.metadata.governingLaw.state}` : ''}
Created ${this.formatDate(this.contract.createdAt)}
Parties ${this.contract.involvedParties?.length || 0} parties
${this.contract.metadata.tags?.length > 0 ? html`
Tags
${this.contract.metadata.tags.map((tag) => html`${tag}`)}
` : ''}
` : ''}
`; } }