892 lines
25 KiB
TypeScript
892 lines
25 KiB
TypeScript
/**
|
|
* @file sdig-contracteditor.ts
|
|
* @description Main contract editor orchestrator component
|
|
*/
|
|
|
|
import {
|
|
DeesElement,
|
|
property,
|
|
state,
|
|
html,
|
|
customElement,
|
|
type TemplateResult,
|
|
css,
|
|
cssManager,
|
|
} from '@design.estate/dees-element';
|
|
|
|
import * as plugins from '../../plugins.js';
|
|
import { createEditorStore, type TEditorStore } from './state.js';
|
|
import {
|
|
type TEditorSection,
|
|
type IEditorState,
|
|
EDITOR_SECTIONS,
|
|
type IContractChangeEventDetail,
|
|
type ISectionChangeEventDetail,
|
|
} from './types.js';
|
|
|
|
// Import sub-components
|
|
import '../sdig-contract-header/sdig-contract-header.js';
|
|
import '../sdig-contract-metadata/sdig-contract-metadata.js';
|
|
import '../sdig-contract-parties/sdig-contract-parties.js';
|
|
import '../sdig-contract-content/sdig-contract-content.js';
|
|
import '../sdig-contract-terms/sdig-contract-terms.js';
|
|
import '../sdig-contract-signatures/sdig-contract-signatures.js';
|
|
import '../sdig-contract-attachments/sdig-contract-attachments.js';
|
|
import '../sdig-contract-collaboration/sdig-contract-collaboration.js';
|
|
import '../sdig-contract-audit/sdig-contract-audit.js';
|
|
import '../sdig-collaboration-sidebar/sdig-collaboration-sidebar.js';
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'sdig-contracteditor': SdigContracteditor;
|
|
}
|
|
}
|
|
|
|
@customElement('sdig-contracteditor')
|
|
export class SdigContracteditor extends DeesElement {
|
|
// ============================================================================
|
|
// STATIC
|
|
// ============================================================================
|
|
|
|
public static demo = () => html`
|
|
<sdig-contracteditor
|
|
.contract=${plugins.sdDemodata.demoContract}
|
|
></sdig-contracteditor>
|
|
`;
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
min-height: 600px;
|
|
}
|
|
|
|
.editor-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background: ${cssManager.bdTheme('#f8f9fa', '#09090b')};
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
}
|
|
|
|
/* Header */
|
|
.editor-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px 24px;
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.contract-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
margin: 0;
|
|
}
|
|
|
|
/* shadcn-style badge */
|
|
.contract-status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 2px 10px;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
border: 1px solid transparent;
|
|
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%)')};
|
|
}
|
|
|
|
.contract-status.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%)')};
|
|
}
|
|
|
|
.contract-status.executed {
|
|
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%)')};
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.dirty-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.dirty-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #f59e0b;
|
|
}
|
|
|
|
.collaborators {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: -8px;
|
|
}
|
|
|
|
.collaborator-avatar {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
border: 2px solid ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: white;
|
|
margin-left: -8px;
|
|
}
|
|
|
|
.collaborator-avatar:first-child {
|
|
margin-left: 0;
|
|
}
|
|
|
|
/* Navigation Tabs */
|
|
.editor-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 0 24px;
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.nav-tab {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 12px 16px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
background: transparent;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.nav-tab:hover {
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
background: ${cssManager.bdTheme('#f3f4f6', '#18181b')};
|
|
}
|
|
|
|
.nav-tab.active {
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
border-bottom-color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
}
|
|
|
|
.nav-tab dees-icon {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.nav-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 20px;
|
|
height: 20px;
|
|
padding: 0 6px;
|
|
border-radius: 10px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
background: ${cssManager.bdTheme('#ef4444', '#dc2626')};
|
|
color: white;
|
|
}
|
|
|
|
/* Main Content Area */
|
|
.editor-main {
|
|
display: flex;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.editor-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 24px;
|
|
}
|
|
|
|
.editor-sidebar {
|
|
width: 320px;
|
|
border-left: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Section placeholder */
|
|
.section-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 400px;
|
|
text-align: center;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.section-placeholder dees-icon {
|
|
font-size: 48px;
|
|
margin-bottom: 16px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.section-placeholder h3 {
|
|
margin: 0 0 8px;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
}
|
|
|
|
.section-placeholder p {
|
|
margin: 0;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* Footer */
|
|
.editor-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 24px;
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
}
|
|
|
|
.footer-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.footer-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* Buttons */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 16px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
border-radius: 6px;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
color: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: ${cssManager.bdTheme('#333333', '#e5e5e5')};
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
|
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
|
|
}
|
|
|
|
.btn-ghost {
|
|
background: transparent;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.btn-ghost:hover {
|
|
background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
}
|
|
|
|
.btn-ghost.active {
|
|
background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
}
|
|
|
|
.btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Loading state */
|
|
.loading-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: ${cssManager.bdTheme('rgba(255,255,255,0.8)', 'rgba(0,0,0,0.8)')};
|
|
z-index: 100;
|
|
}
|
|
|
|
/* Overview section layout */
|
|
.overview-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
`,
|
|
];
|
|
|
|
// ============================================================================
|
|
// PROPERTIES
|
|
// ============================================================================
|
|
|
|
@property({ type: Object })
|
|
public accessor contract: plugins.sdInterfaces.IPortableContract | null = null;
|
|
|
|
@property({ type: Boolean })
|
|
public accessor showSidebar: boolean = true;
|
|
|
|
@property({ type: String })
|
|
public accessor initialSection: TEditorSection = 'overview';
|
|
|
|
// ============================================================================
|
|
// STATE
|
|
// ============================================================================
|
|
|
|
@state()
|
|
private accessor editorState: IEditorState | null = null;
|
|
|
|
// ============================================================================
|
|
// INSTANCE
|
|
// ============================================================================
|
|
|
|
private store: TEditorStore | null = null;
|
|
private unsubscribe: (() => void) | null = null;
|
|
private storeReady: Promise<void>;
|
|
private resolveStoreReady!: () => void;
|
|
|
|
constructor() {
|
|
super();
|
|
this.storeReady = new Promise((resolve) => {
|
|
this.resolveStoreReady = resolve;
|
|
});
|
|
}
|
|
|
|
// ============================================================================
|
|
// LIFECYCLE
|
|
// ============================================================================
|
|
|
|
public connectedCallback() {
|
|
super.connectedCallback();
|
|
this.initStore();
|
|
}
|
|
|
|
private async initStore() {
|
|
this.store = await createEditorStore();
|
|
this.unsubscribe = this.store.subscribe((state) => {
|
|
this.editorState = state;
|
|
});
|
|
|
|
// Set initial section
|
|
this.store.setActiveSection(this.initialSection);
|
|
this.resolveStoreReady();
|
|
|
|
// If contract was already set, apply it now
|
|
if (this.contract) {
|
|
this.store.setContract(this.contract);
|
|
}
|
|
}
|
|
|
|
public disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
if (this.unsubscribe) {
|
|
this.unsubscribe();
|
|
}
|
|
}
|
|
|
|
public async updated(changedProperties: Map<string, unknown>) {
|
|
if (changedProperties.has('contract') && this.contract) {
|
|
await this.storeReady;
|
|
this.store?.setContract(this.contract);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EVENT HANDLERS
|
|
// ============================================================================
|
|
|
|
private handleSectionChange(section: TEditorSection) {
|
|
const previousSection = this.editorState?.activeSection || 'overview';
|
|
this.store?.setActiveSection(section);
|
|
|
|
this.dispatchEvent(
|
|
new CustomEvent<ISectionChangeEventDetail>('section-change', {
|
|
detail: { section, previousSection },
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
private handleSave() {
|
|
if (!this.editorState?.contract) return;
|
|
|
|
this.store?.setSaving(true);
|
|
|
|
this.dispatchEvent(
|
|
new CustomEvent('contract-save', {
|
|
detail: {
|
|
contract: this.editorState.contract,
|
|
isDraft: this.editorState.contract.lifecycle.currentStatus === 'draft',
|
|
},
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
private handleDiscard() {
|
|
this.store?.discardChanges();
|
|
this.dispatchEvent(
|
|
new CustomEvent('contract-discard', {
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
private handleUndo() {
|
|
this.store?.undo();
|
|
}
|
|
|
|
private handleRedo() {
|
|
this.store?.redo();
|
|
}
|
|
|
|
private handleCommentClick(e: CustomEvent) {
|
|
// Navigate to collaboration section and highlight comment
|
|
this.store?.setActiveSection('collaboration');
|
|
this.dispatchEvent(
|
|
new CustomEvent('comment-focus', {
|
|
detail: e.detail,
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
private handleSuggestionClick(e: CustomEvent) {
|
|
// Navigate to collaboration section and highlight suggestion
|
|
this.store?.setActiveSection('collaboration');
|
|
this.dispatchEvent(
|
|
new CustomEvent('suggestion-focus', {
|
|
detail: e.detail,
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
private handleSidebarAddComment(e: CustomEvent) {
|
|
this.dispatchEvent(
|
|
new CustomEvent('comment-added', {
|
|
detail: e.detail,
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
// ============================================================================
|
|
// PUBLIC API
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Update a field in the contract
|
|
*/
|
|
public updateField(path: string, value: unknown, description?: string) {
|
|
this.store?.updateContract(path, value, description);
|
|
|
|
this.dispatchEvent(
|
|
new CustomEvent<IContractChangeEventDetail>('contract-change', {
|
|
detail: { path, value, source: 'user' },
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get current contract state
|
|
*/
|
|
public getContract(): plugins.sdInterfaces.IPortableContract | null {
|
|
return this.editorState?.contract || null;
|
|
}
|
|
|
|
/**
|
|
* Mark contract as saved externally
|
|
*/
|
|
public markSaved() {
|
|
this.store?.markSaved();
|
|
}
|
|
|
|
// ============================================================================
|
|
// RENDER HELPERS
|
|
// ============================================================================
|
|
|
|
private getStatusClass(status: string): string {
|
|
if (status === 'draft' || status === 'internal_review') return 'draft';
|
|
if (status === 'executed' || status === 'active') return 'executed';
|
|
return '';
|
|
}
|
|
|
|
private formatStatus(status: string): string {
|
|
return status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
}
|
|
|
|
private handleFieldChange(e: CustomEvent<{ path: string; value: unknown }>) {
|
|
const { path, value } = e.detail;
|
|
this.updateField(path, value);
|
|
}
|
|
|
|
private renderSectionContent(): TemplateResult {
|
|
const section = this.editorState?.activeSection || 'overview';
|
|
const contract = this.editorState?.contract;
|
|
const sectionConfig = EDITOR_SECTIONS.find((s) => s.id === section);
|
|
|
|
// Render section based on active tab
|
|
switch (section) {
|
|
case 'overview':
|
|
return this.renderOverviewSection();
|
|
case 'parties':
|
|
return this.renderPartiesSection();
|
|
case 'content':
|
|
return this.renderContentSection();
|
|
case 'terms':
|
|
return this.renderTermsSection();
|
|
case 'signatures':
|
|
return this.renderSignaturesSection();
|
|
case 'attachments':
|
|
return this.renderAttachmentsSection();
|
|
case 'collaboration':
|
|
return this.renderCollaborationSection();
|
|
case 'audit':
|
|
return this.renderAuditSection();
|
|
default:
|
|
return this.renderPlaceholder(sectionConfig, 'This section is being implemented...');
|
|
}
|
|
}
|
|
|
|
private renderOverviewSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<div class="overview-section">
|
|
<sdig-contract-header
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-header>
|
|
|
|
<sdig-contract-metadata
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-metadata>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderPartiesSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<sdig-contract-parties
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-parties>
|
|
`;
|
|
}
|
|
|
|
private renderContentSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<sdig-contract-content
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-content>
|
|
`;
|
|
}
|
|
|
|
private renderTermsSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<sdig-contract-terms
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-terms>
|
|
`;
|
|
}
|
|
|
|
private renderSignaturesSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<sdig-contract-signatures
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-signatures>
|
|
`;
|
|
}
|
|
|
|
private renderAttachmentsSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<sdig-contract-attachments
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-attachments>
|
|
`;
|
|
}
|
|
|
|
private renderCollaborationSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<sdig-contract-collaboration
|
|
.contract=${contract}
|
|
@field-change=${this.handleFieldChange}
|
|
></sdig-contract-collaboration>
|
|
`;
|
|
}
|
|
|
|
private renderAuditSection(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
if (!contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
return html`
|
|
<sdig-contract-audit
|
|
.contract=${contract}
|
|
></sdig-contract-audit>
|
|
`;
|
|
}
|
|
|
|
private renderPlaceholder(sectionConfig: typeof EDITOR_SECTIONS[0] | undefined, message: string): TemplateResult {
|
|
return html`
|
|
<div class="section-placeholder">
|
|
<dees-icon .icon=${sectionConfig?.icon || 'lucide:File'}></dees-icon>
|
|
<h3>${sectionConfig?.label || 'Section'}</h3>
|
|
<p>${message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// ============================================================================
|
|
// RENDER
|
|
// ============================================================================
|
|
|
|
public render(): TemplateResult {
|
|
const contract = this.editorState?.contract;
|
|
const activeSection = this.editorState?.activeSection || 'overview';
|
|
const isDirty = this.editorState?.isDirty || false;
|
|
const isSaving = this.editorState?.isSaving || false;
|
|
const collaborators = this.editorState?.activeCollaborators || [];
|
|
|
|
return html`
|
|
<div class="editor-container">
|
|
<!-- Header -->
|
|
<div class="editor-header">
|
|
<div class="header-left">
|
|
<h1 class="contract-title">${contract?.title || 'Untitled Contract'}</h1>
|
|
${contract?.lifecycle?.currentStatus
|
|
? html`
|
|
<span class="contract-status ${this.getStatusClass(contract.lifecycle.currentStatus)}">
|
|
${this.formatStatus(contract.lifecycle.currentStatus)}
|
|
</span>
|
|
`
|
|
: ''}
|
|
</div>
|
|
<div class="header-right">
|
|
${isDirty
|
|
? html`
|
|
<div class="dirty-indicator">
|
|
<span class="dirty-dot"></span>
|
|
<span>Unsaved changes</span>
|
|
</div>
|
|
`
|
|
: ''}
|
|
${collaborators.length > 0
|
|
? html`
|
|
<div class="collaborators">
|
|
${collaborators.slice(0, 3).map(
|
|
(c) => html`
|
|
<div
|
|
class="collaborator-avatar"
|
|
style="background: ${c.color}"
|
|
title="${c.displayName}"
|
|
>
|
|
${c.displayName.charAt(0).toUpperCase()}
|
|
</div>
|
|
`
|
|
)}
|
|
${collaborators.length > 3
|
|
? html`
|
|
<div class="collaborator-avatar" style="background: #6b7280">
|
|
+${collaborators.length - 3}
|
|
</div>
|
|
`
|
|
: ''}
|
|
</div>
|
|
`
|
|
: ''}
|
|
<button class="btn btn-ghost" @click=${this.handleUndo} ?disabled=${!this.store?.canUndo()}>
|
|
<dees-icon .icon=${'lucide:Undo2'}></dees-icon>
|
|
</button>
|
|
<button class="btn btn-ghost" @click=${this.handleRedo} ?disabled=${!this.store?.canRedo()}>
|
|
<dees-icon .icon=${'lucide:Redo2'}></dees-icon>
|
|
</button>
|
|
<button
|
|
class="btn btn-ghost ${this.showSidebar ? 'active' : ''}"
|
|
@click=${() => (this.showSidebar = !this.showSidebar)}
|
|
title="${this.showSidebar ? 'Hide sidebar' : 'Show sidebar'}"
|
|
>
|
|
<dees-icon .icon=${'lucide:PanelRight'}></dees-icon>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="editor-nav">
|
|
${EDITOR_SECTIONS.map(
|
|
(section) => html`
|
|
<button
|
|
class="nav-tab ${activeSection === section.id ? 'active' : ''}"
|
|
@click=${() => this.handleSectionChange(section.id)}
|
|
?disabled=${section.disabled}
|
|
>
|
|
<dees-icon .icon=${section.icon}></dees-icon>
|
|
<span>${section.label}</span>
|
|
${section.badge
|
|
? html`<span class="nav-badge">${section.badge}</span>`
|
|
: ''}
|
|
</button>
|
|
`
|
|
)}
|
|
</nav>
|
|
|
|
<!-- Main Content -->
|
|
<div class="editor-main">
|
|
<div class="editor-content">
|
|
${this.renderSectionContent()}
|
|
</div>
|
|
${this.showSidebar
|
|
? html`
|
|
<aside class="editor-sidebar">
|
|
<sdig-collaboration-sidebar
|
|
.contract=${contract}
|
|
@comment-click=${this.handleCommentClick}
|
|
@suggestion-click=${this.handleSuggestionClick}
|
|
@add-comment=${this.handleSidebarAddComment}
|
|
></sdig-collaboration-sidebar>
|
|
</aside>
|
|
`
|
|
: ''}
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="editor-footer">
|
|
<div class="footer-left">
|
|
${contract?.updatedAt
|
|
? html`<span>Last updated: ${new Date(contract.updatedAt).toLocaleString()}</span>`
|
|
: ''}
|
|
${contract?.versionHistory?.currentVersionId
|
|
? html`<span>Version: ${contract.versionHistory.currentVersionId}</span>`
|
|
: ''}
|
|
</div>
|
|
<div class="footer-right">
|
|
${isDirty
|
|
? html`
|
|
<button class="btn btn-secondary" @click=${this.handleDiscard}>
|
|
Discard
|
|
</button>
|
|
`
|
|
: ''}
|
|
<button
|
|
class="btn btn-primary"
|
|
@click=${this.handleSave}
|
|
?disabled=${!isDirty || isSaving}
|
|
>
|
|
${isSaving ? 'Saving...' : 'Save'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
${this.editorState?.isLoading
|
|
? html`
|
|
<div class="loading-overlay">
|
|
<dees-spinner></dees-spinner>
|
|
</div>
|
|
`
|
|
: ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|