/** * @file sdig-contract-metadata.ts * @description Contract metadata editor component */ 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-metadata': SdigContractMetadata; } } // Type-safe options arrays const CONTRACT_CATEGORIES: Array<{ value: plugins.sdInterfaces.TContractCategory; label: string }> = [ { value: 'employment', label: 'Employment' }, { value: 'service', label: 'Service Agreement' }, { value: 'sales', label: 'Sales' }, { value: 'lease', label: 'Lease / Rental' }, { value: 'license', label: 'License' }, { value: 'partnership', label: 'Partnership' }, { value: 'confidentiality', label: 'Confidentiality / NDA' }, { value: 'financial', label: 'Financial' }, { value: 'real_estate', label: 'Real Estate' }, { value: 'intellectual_property', label: 'Intellectual Property' }, { value: 'government', label: 'Government' }, { value: 'construction', label: 'Construction' }, { value: 'healthcare', label: 'Healthcare' }, { value: 'insurance', label: 'Insurance' }, { value: 'other', label: 'Other' }, ]; const CONFIDENTIALITY_LEVELS: Array<{ value: plugins.sdInterfaces.TConfidentialityLevel; label: string }> = [ { value: 'public', label: 'Public' }, { value: 'internal', label: 'Internal' }, { value: 'confidential', label: 'Confidential' }, { value: 'restricted', label: 'Restricted' }, ]; const DISPUTE_RESOLUTIONS: Array<{ value: plugins.sdInterfaces.TDisputeResolution; label: string }> = [ { value: 'litigation', label: 'Litigation' }, { value: 'arbitration', label: 'Arbitration' }, { value: 'mediation', label: 'Mediation' }, { value: 'negotiation', label: 'Negotiation' }, ]; const COMMON_LANGUAGES = [ { value: 'en', label: 'English' }, { value: 'de', label: 'German' }, { value: 'fr', label: 'French' }, { value: 'es', label: 'Spanish' }, { value: 'it', label: 'Italian' }, { value: 'pt', label: 'Portuguese' }, { value: 'nl', label: 'Dutch' }, { value: 'pl', label: 'Polish' }, { value: 'sv', label: 'Swedish' }, { value: 'da', label: 'Danish' }, { value: 'fi', label: 'Finnish' }, { value: 'no', label: 'Norwegian' }, { value: 'zh', label: 'Chinese' }, { value: 'ja', label: 'Japanese' }, { value: 'ko', label: 'Korean' }, { value: 'ar', label: 'Arabic' }, { value: 'ru', label: 'Russian' }, ]; @customElement('sdig-contract-metadata') export class SdigContractMetadata extends DeesElement { // ============================================================================ // STATIC // ============================================================================ public static demo = () => html` `; public static styles = [ cssManager.defaultStyles, css` :host { display: block; } .metadata-container { display: flex; flex-direction: column; gap: 24px; } /* Section cards */ .section-card { background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')}; border-radius: 12px; overflow: hidden; } .section-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; background: ${cssManager.bdTheme('#f9fafb', '#111111')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')}; } .section-title { display: flex; align-items: center; gap: 10px; font-size: 15px; font-weight: 600; color: ${cssManager.bdTheme('#111111', '#fafafa')}; } .section-title dees-icon { font-size: 18px; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; } .section-content { padding: 20px; } /* Form grid */ .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; } .form-group { display: flex; flex-direction: column; gap: 6px; } .form-group.full-width { grid-column: 1 / -1; } .form-label { font-size: 13px; font-weight: 500; color: ${cssManager.bdTheme('#374151', '#d1d5db')}; } .form-label .required { color: #ef4444; margin-left: 2px; } .form-description { font-size: 12px; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; margin-top: 4px; } /* Input styles */ .form-input, .form-select, .form-textarea { width: 100%; padding: 10px 12px; font-size: 14px; color: ${cssManager.bdTheme('#111111', '#fafafa')}; background: ${cssManager.bdTheme('#ffffff', '#18181b')}; border: 1px solid ${cssManager.bdTheme('#d1d5db', '#3f3f46')}; border-radius: 8px; outline: none; transition: all 0.15s ease; } .form-input:focus, .form-select:focus, .form-textarea:focus { border-color: ${cssManager.bdTheme('#111111', '#fafafa')}; box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(0,0,0,0.05)', 'rgba(255,255,255,0.05)')}; } .form-input::placeholder, .form-textarea::placeholder { color: ${cssManager.bdTheme('#9ca3af', '#6b7280')}; } .form-input:disabled, .form-select:disabled, .form-textarea:disabled { opacity: 0.6; cursor: not-allowed; } .form-select { appearance: none; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); background-position: right 10px center; background-repeat: no-repeat; background-size: 16px; padding-right: 36px; } .form-textarea { min-height: 80px; resize: vertical; } /* Radio group */ .radio-group { display: flex; flex-wrap: wrap; gap: 12px; } .radio-option { display: flex; align-items: center; gap: 8px; cursor: pointer; } .radio-option input[type="radio"] { width: 18px; height: 18px; accent-color: ${cssManager.bdTheme('#111111', '#fafafa')}; } .radio-option span { font-size: 14px; color: ${cssManager.bdTheme('#374151', '#d1d5db')}; } /* Tags input */ .tags-input-container { display: flex; flex-wrap: wrap; gap: 8px; padding: 8px 12px; min-height: 44px; background: ${cssManager.bdTheme('#ffffff', '#18181b')}; border: 1px solid ${cssManager.bdTheme('#d1d5db', '#3f3f46')}; border-radius: 8px; cursor: text; } .tags-input-container:focus-within { border-color: ${cssManager.bdTheme('#111111', '#fafafa')}; box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(0,0,0,0.05)', 'rgba(255,255,255,0.05)')}; } .tag-item { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: ${cssManager.bdTheme('#f3f4f6', '#27272a')}; border-radius: 6px; font-size: 13px; color: ${cssManager.bdTheme('#374151', '#d1d5db')}; } .tag-remove { display: flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: transparent; border: none; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; cursor: pointer; font-size: 14px; line-height: 1; } .tag-remove:hover { background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')}; color: ${cssManager.bdTheme('#111111', '#fafafa')}; } .tags-input { flex: 1; min-width: 120px; border: none; background: transparent; padding: 4px 0; font-size: 14px; color: ${cssManager.bdTheme('#111111', '#fafafa')}; outline: none; } .tags-input::placeholder { color: ${cssManager.bdTheme('#9ca3af', '#6b7280')}; } /* Divider */ .section-divider { border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')}; margin: 20px 0; } /* Collapsible sections */ .collapsible-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 0; cursor: pointer; user-select: none; } .collapsible-header:hover .collapse-icon { color: ${cssManager.bdTheme('#111111', '#fafafa')}; } .collapse-icon { font-size: 16px; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; transition: transform 0.2s ease; } .collapse-icon.expanded { transform: rotate(180deg); } .collapsible-content { overflow: hidden; max-height: 0; opacity: 0; transition: all 0.3s ease; } .collapsible-content.expanded { max-height: 1000px; opacity: 1; } `, ]; // ============================================================================ // PROPERTIES // ============================================================================ @property({ type: Object }) public accessor contract: plugins.sdInterfaces.IPortableContract | null = null; @property({ type: Boolean }) public accessor readonly: boolean = false; // ============================================================================ // STATE // ============================================================================ @state() private accessor showArbitrationFields: boolean = false; @state() private accessor newTag: string = ''; // ============================================================================ // LIFECYCLE // ============================================================================ public updated(changedProperties: Map) { if (changedProperties.has('contract') && this.contract?.metadata?.governingLaw) { this.showArbitrationFields = this.contract.metadata.governingLaw.disputeResolution === 'arbitration'; } } // ============================================================================ // EVENT HANDLERS // ============================================================================ private handleFieldChange(path: string, value: unknown) { this.dispatchEvent( new CustomEvent('field-change', { detail: { path, value }, bubbles: true, composed: true, }) ); } private handleInputChange(path: string, e: Event) { const input = e.target as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; this.handleFieldChange(path, input.value); } private handleRadioChange(path: string, value: string) { this.handleFieldChange(path, value); // Special handling for dispute resolution if (path === 'metadata.governingLaw.disputeResolution') { this.showArbitrationFields = value === 'arbitration'; } } private handleTagAdd(e: KeyboardEvent) { if (e.key === 'Enter' && this.newTag.trim()) { e.preventDefault(); const currentTags = this.contract?.metadata.tags || []; if (!currentTags.includes(this.newTag.trim())) { this.handleFieldChange('metadata.tags', [...currentTags, this.newTag.trim()]); } this.newTag = ''; } } private handleTagRemove(tag: string) { const currentTags = this.contract?.metadata.tags || []; this.handleFieldChange( 'metadata.tags', currentTags.filter((t) => t !== tag) ); } // ============================================================================ // RENDER // ============================================================================ public render(): TemplateResult { if (!this.contract) { return html`No contract loaded`; } if (!this.contract.metadata) { return html`Contract metadata not available`; } const metadata = this.contract.metadata; return html` Basic Information Contract Number this.handleInputChange('metadata.contractNumber', e)} /> Category * this.handleInputChange('metadata.category', e)} > ${CONTRACT_CATEGORIES.map( (cat) => html` ${cat.label} ` )} Contract Type * this.handleInputChange('metadata.contractType', e)} /> Specific contract type identifier Confidentiality Level ${CONFIDENTIALITY_LEVELS.map( (level) => html` this.handleRadioChange('metadata.confidentialityLevel', level.value)} /> ${level.label} ` )} Tags ${metadata.tags.map( (tag) => html` ${tag} ${!this.readonly ? html` this.handleTagRemove(tag)}>× ` : ''} ` )} ${!this.readonly ? html` (this.newTag = (e.target as HTMLInputElement).value)} @keydown=${this.handleTagAdd} /> ` : ''} Language Settings Primary Language * this.handleInputChange('metadata.language', e)} > ${COMMON_LANGUAGES.map( (lang) => html` ${lang.label} ` )} Binding Language this.handleInputChange('metadata.bindingLanguage', e)} > Same as primary ${COMMON_LANGUAGES.map( (lang) => html` ${lang.label} ` )} Language that takes precedence in case of conflicts Governing Law & Jurisdiction Country * this.handleInputChange('metadata.governingLaw.country', e)} /> State / Province this.handleInputChange('metadata.governingLaw.state', e)} /> Dispute Jurisdiction this.handleInputChange('metadata.governingLaw.disputeJurisdiction', e)} /> Dispute Resolution Method ${DISPUTE_RESOLUTIONS.map( (res) => html` this.handleRadioChange('metadata.governingLaw.disputeResolution', res.value)} /> ${res.label} ` )} ${this.showArbitrationFields ? html` Arbitration Institution this.handleInputChange('metadata.governingLaw.arbitrationInstitution', e)} /> Arbitration Rules this.handleInputChange('metadata.governingLaw.arbitrationRules', e)} /> Seat of Arbitration this.handleInputChange('metadata.governingLaw.arbitrationSeat', e)} /> Number of Arbitrators this.handleFieldChange( 'metadata.governingLaw.numberOfArbitrators', parseInt((e.target as HTMLSelectElement).value, 10) )} > 1 Arbitrator 3 Arbitrators 5 Arbitrators Proceedings Language this.handleInputChange('metadata.governingLaw.proceedingsLanguage', e)} > Select language ${COMMON_LANGUAGES.map( (lang) => html` ${lang.label} ` )} ` : ''} References & Integration Internal Reference this.handleInputChange('metadata.internalReference', e)} /> External Reference this.handleInputChange('metadata.externalReference', e)} /> Template ID this.handleInputChange('metadata.templateId', e)} /> Parent Contract ID this.handleInputChange('metadata.parentContractId', e)} /> `; } }
Specific contract type identifier
Language that takes precedence in case of conflicts