feat(catalog): add ContractEditor and many editor subcomponents; implement SignPad and SignBox; update README and bump dependencies
This commit is contained in:
@@ -0,0 +1,806 @@
|
||||
/**
|
||||
* @file sdig-contract-attachments.ts
|
||||
* @description Contract attachments and prior contracts manager
|
||||
*/
|
||||
|
||||
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-attachments': SdigContractAttachments;
|
||||
}
|
||||
}
|
||||
|
||||
// Attachment interface
|
||||
interface IAttachment {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'document' | 'image' | 'spreadsheet' | 'pdf' | 'other';
|
||||
mimeType: string;
|
||||
size: number;
|
||||
uploadedAt: number;
|
||||
uploadedBy: string;
|
||||
description?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
// File type configuration
|
||||
const FILE_TYPES = {
|
||||
document: { icon: 'lucide:file-text', color: '#3b82f6', label: 'Document' },
|
||||
image: { icon: 'lucide:image', color: '#10b981', label: 'Image' },
|
||||
spreadsheet: { icon: 'lucide:sheet', color: '#22c55e', label: 'Spreadsheet' },
|
||||
pdf: { icon: 'lucide:file-type', color: '#ef4444', label: 'PDF' },
|
||||
other: { icon: 'lucide:file', color: '#6b7280', label: 'File' },
|
||||
};
|
||||
|
||||
@customElement('sdig-contract-attachments')
|
||||
export class SdigContractAttachments extends DeesElement {
|
||||
// ============================================================================
|
||||
// STATIC
|
||||
// ============================================================================
|
||||
|
||||
public static demo = () => html`
|
||||
<sdig-contract-attachments
|
||||
.contract=${plugins.sdDemodata.demoContract}
|
||||
></sdig-contract-attachments>
|
||||
`;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.attachments-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* Section card */
|
||||
.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-count {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Upload zone */
|
||||
.upload-zone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
border: 2px dashed ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
|
||||
border-radius: 12px;
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.upload-zone:hover {
|
||||
border-color: ${cssManager.bdTheme('#9ca3af', '#52525b')};
|
||||
background: ${cssManager.bdTheme('#f3f4f6', '#18181b')};
|
||||
}
|
||||
|
||||
.upload-zone.dragging {
|
||||
border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
background: ${cssManager.bdTheme('#eff6ff', '#172554')};
|
||||
}
|
||||
|
||||
.upload-zone-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.upload-zone-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upload-zone-subtitle {
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.upload-zone-hint {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
|
||||
}
|
||||
|
||||
/* Attachments list */
|
||||
.attachments-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
border-radius: 10px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.attachment-item:hover {
|
||||
border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
|
||||
}
|
||||
|
||||
.attachment-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attachment-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.attachment-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
}
|
||||
|
||||
.attachment-meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.attachment-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Prior contracts */
|
||||
.prior-contracts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.prior-contract-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.prior-contract-item:hover {
|
||||
border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
|
||||
background: ${cssManager.bdTheme('#f3f4f6', '#18181b')};
|
||||
}
|
||||
|
||||
.prior-contract-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
background: ${cssManager.bdTheme('#dbeafe', '#1e3a5f')};
|
||||
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.prior-contract-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.prior-contract-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.prior-contract-context {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.prior-contract-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
}
|
||||
|
||||
.empty-state dees-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-state h4 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Storage summary */
|
||||
.storage-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.storage-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.storage-label {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.storage-bar {
|
||||
height: 6px;
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.storage-fill {
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.storage-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.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')};
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
|
||||
}
|
||||
|
||||
/* Type badge */
|
||||
.type-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// PROPERTIES
|
||||
// ============================================================================
|
||||
|
||||
@property({ type: Object })
|
||||
public accessor contract: plugins.sdInterfaces.IPortableContract | null = null;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public accessor readonly: boolean = false;
|
||||
|
||||
// ============================================================================
|
||||
// STATE
|
||||
// ============================================================================
|
||||
|
||||
@state()
|
||||
private accessor isDragging: boolean = false;
|
||||
|
||||
// Demo attachments data
|
||||
@state()
|
||||
private accessor attachments: IAttachment[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Employment_Terms_v2.pdf',
|
||||
type: 'pdf',
|
||||
mimeType: 'application/pdf',
|
||||
size: 245760,
|
||||
uploadedAt: Date.now() - 86400000 * 3,
|
||||
uploadedBy: 'employer',
|
||||
description: 'Original employment terms document',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'ID_Verification.png',
|
||||
type: 'image',
|
||||
mimeType: 'image/png',
|
||||
size: 1024000,
|
||||
uploadedAt: Date.now() - 86400000,
|
||||
uploadedBy: 'employee',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Tax_Information.xlsx',
|
||||
type: 'spreadsheet',
|
||||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
size: 52480,
|
||||
uploadedAt: Date.now() - 86400000 * 2,
|
||||
uploadedBy: 'employer',
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
private handleFieldChange(path: string, value: unknown) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('field-change', {
|
||||
detail: { path, value },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private handleDragEnter(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.isDragging = true;
|
||||
}
|
||||
|
||||
private handleDragLeave(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.isDragging = false;
|
||||
}
|
||||
|
||||
private handleDragOver(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
private handleDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.isDragging = false;
|
||||
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
this.handleFiles(files);
|
||||
}
|
||||
}
|
||||
|
||||
private handleFileSelect() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.multiple = true;
|
||||
input.onchange = () => {
|
||||
if (input.files && input.files.length > 0) {
|
||||
this.handleFiles(input.files);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
private handleFiles(files: FileList) {
|
||||
// Demo: just add to list
|
||||
Array.from(files).forEach((file) => {
|
||||
const newAttachment: IAttachment = {
|
||||
id: `att-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
name: file.name,
|
||||
type: this.getFileType(file.type),
|
||||
mimeType: file.type,
|
||||
size: file.size,
|
||||
uploadedAt: Date.now(),
|
||||
uploadedBy: 'user',
|
||||
};
|
||||
this.attachments = [...this.attachments, newAttachment];
|
||||
});
|
||||
}
|
||||
|
||||
private handleDeleteAttachment(attachmentId: string) {
|
||||
this.attachments = this.attachments.filter((a) => a.id !== attachmentId);
|
||||
}
|
||||
|
||||
private handleAddPriorContract() {
|
||||
// TODO: Open prior contract picker modal
|
||||
}
|
||||
|
||||
private handleRemovePriorContract(index: number) {
|
||||
if (!this.contract) return;
|
||||
const updatedPriorContracts = [...this.contract.priorContracts];
|
||||
updatedPriorContracts.splice(index, 1);
|
||||
this.handleFieldChange('priorContracts', updatedPriorContracts);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPERS
|
||||
// ============================================================================
|
||||
|
||||
private getFileType(mimeType: string): IAttachment['type'] {
|
||||
if (mimeType.includes('pdf')) return 'pdf';
|
||||
if (mimeType.includes('image')) return 'image';
|
||||
if (mimeType.includes('spreadsheet') || mimeType.includes('excel')) return 'spreadsheet';
|
||||
if (mimeType.includes('document') || mimeType.includes('word')) return 'document';
|
||||
return 'other';
|
||||
}
|
||||
|
||||
private getFileTypeConfig(type: IAttachment['type']) {
|
||||
return FILE_TYPES[type] || FILE_TYPES.other;
|
||||
}
|
||||
|
||||
private formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
private formatDate(timestamp: number): string {
|
||||
return new Date(timestamp).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
private getTotalSize(): number {
|
||||
return this.attachments.reduce((sum, a) => sum + a.size, 0);
|
||||
}
|
||||
|
||||
private getPartyName(roleId: string): string {
|
||||
const role = this.contract?.availableRoles.find((r) => r.id === roleId);
|
||||
return role?.name || roleId;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
|
||||
public render(): TemplateResult {
|
||||
if (!this.contract) {
|
||||
return html`<div>No contract loaded</div>`;
|
||||
}
|
||||
|
||||
const totalSize = this.getTotalSize();
|
||||
const maxSize = 50 * 1024 * 1024; // 50MB demo limit
|
||||
const usagePercent = Math.min((totalSize / maxSize) * 100, 100);
|
||||
|
||||
return html`
|
||||
<div class="attachments-container">
|
||||
<!-- Attachments Section -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<div class="section-title">
|
||||
<dees-icon .iconFA=${'lucide:paperclip'}></dees-icon>
|
||||
Attachments
|
||||
</div>
|
||||
<span class="section-count">${this.attachments.length} files</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<!-- Storage summary -->
|
||||
<div class="storage-summary">
|
||||
<div class="storage-info">
|
||||
<div class="storage-label">Storage used</div>
|
||||
<div class="storage-bar">
|
||||
<div class="storage-fill" style="width: ${usagePercent}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storage-text">
|
||||
${this.formatFileSize(totalSize)} / ${this.formatFileSize(maxSize)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload zone -->
|
||||
${!this.readonly
|
||||
? html`
|
||||
<div
|
||||
class="upload-zone ${this.isDragging ? 'dragging' : ''}"
|
||||
@dragenter=${this.handleDragEnter}
|
||||
@dragleave=${this.handleDragLeave}
|
||||
@dragover=${this.handleDragOver}
|
||||
@drop=${this.handleDrop}
|
||||
@click=${this.handleFileSelect}
|
||||
>
|
||||
<div class="upload-zone-icon">
|
||||
<dees-icon .iconFA=${'lucide:upload-cloud'}></dees-icon>
|
||||
</div>
|
||||
<div class="upload-zone-title">Drop files here or click to upload</div>
|
||||
<div class="upload-zone-subtitle">Add supporting documents, images, or spreadsheets</div>
|
||||
<div class="upload-zone-hint">PDF, DOCX, XLSX, PNG, JPG up to 10MB each</div>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
|
||||
<!-- Attachments list -->
|
||||
${this.attachments.length > 0
|
||||
? html`
|
||||
<div class="attachments-list" style="margin-top: 20px;">
|
||||
${this.attachments.map((attachment) => this.renderAttachmentItem(attachment))}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="empty-state" style="margin-top: 20px;">
|
||||
<dees-icon .iconFA=${'lucide:file-x'}></dees-icon>
|
||||
<h4>No Attachments</h4>
|
||||
<p>Upload files to attach them to this contract</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prior Contracts Section -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<div class="section-title">
|
||||
<dees-icon .iconFA=${'lucide:files'}></dees-icon>
|
||||
Prior Contracts
|
||||
</div>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<button class="btn btn-secondary" @click=${this.handleAddPriorContract}>
|
||||
<dees-icon .iconFA=${'lucide:plus'}></dees-icon>
|
||||
Link Contract
|
||||
</button>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
<div class="section-content">
|
||||
${this.contract.priorContracts.length > 0
|
||||
? html`
|
||||
<div class="prior-contracts-list">
|
||||
${this.contract.priorContracts.map((priorContract, index) =>
|
||||
this.renderPriorContractItem(priorContract, index)
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="empty-state">
|
||||
<dees-icon .iconFA=${'lucide:link'}></dees-icon>
|
||||
<h4>No Prior Contracts</h4>
|
||||
<p>Link related or predecessor contracts here</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderAttachmentItem(attachment: IAttachment): TemplateResult {
|
||||
const typeConfig = this.getFileTypeConfig(attachment.type);
|
||||
|
||||
return html`
|
||||
<div class="attachment-item">
|
||||
<div class="attachment-icon" style="background: ${typeConfig.color}20; color: ${typeConfig.color}">
|
||||
<dees-icon .iconFA=${typeConfig.icon}></dees-icon>
|
||||
</div>
|
||||
<div class="attachment-info">
|
||||
<div class="attachment-name">${attachment.name}</div>
|
||||
<div class="attachment-meta">
|
||||
<span class="type-badge">${typeConfig.label}</span>
|
||||
<span class="attachment-meta-item">
|
||||
${this.formatFileSize(attachment.size)}
|
||||
</span>
|
||||
<span class="attachment-meta-item">
|
||||
<dees-icon .iconFA=${'lucide:calendar'}></dees-icon>
|
||||
${this.formatDate(attachment.uploadedAt)}
|
||||
</span>
|
||||
<span class="attachment-meta-item">
|
||||
<dees-icon .iconFA=${'lucide:user'}></dees-icon>
|
||||
${this.getPartyName(attachment.uploadedBy)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attachment-actions">
|
||||
<button class="btn btn-ghost" title="Download">
|
||||
<dees-icon .iconFA=${'lucide:download'}></dees-icon>
|
||||
</button>
|
||||
<button class="btn btn-ghost" title="Preview">
|
||||
<dees-icon .iconFA=${'lucide:eye'}></dees-icon>
|
||||
</button>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<button
|
||||
class="btn btn-ghost btn-danger"
|
||||
title="Delete"
|
||||
@click=${() => this.handleDeleteAttachment(attachment.id)}
|
||||
>
|
||||
<dees-icon .iconFA=${'lucide:trash-2'}></dees-icon>
|
||||
</button>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderPriorContractItem(priorContract: plugins.sdInterfaces.IPortableContract, index: number): TemplateResult {
|
||||
return html`
|
||||
<div class="prior-contract-item">
|
||||
<div class="prior-contract-icon">
|
||||
<dees-icon .iconFA=${'lucide:file-text'}></dees-icon>
|
||||
</div>
|
||||
<div class="prior-contract-info">
|
||||
<div class="prior-contract-title">${priorContract.title}</div>
|
||||
<div class="prior-contract-context">${priorContract.context || 'No description'}</div>
|
||||
</div>
|
||||
<div class="prior-contract-actions">
|
||||
<button class="btn btn-secondary btn-sm">
|
||||
<dees-icon .iconFA=${'lucide:external-link'}></dees-icon>
|
||||
View
|
||||
</button>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<button
|
||||
class="btn btn-ghost btn-danger"
|
||||
@click=${() => this.handleRemovePriorContract(index)}
|
||||
>
|
||||
<dees-icon .iconFA=${'lucide:unlink'}></dees-icon>
|
||||
</button>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user