841 lines
24 KiB
TypeScript
841 lines
24 KiB
TypeScript
/**
|
|
* @file sdig-contract-signatures.ts
|
|
* @description Contract signature fields manager 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-signatures': SdigContractSignatures;
|
|
}
|
|
}
|
|
|
|
// Signature field interface (for future interface updates)
|
|
interface ISignatureField {
|
|
id: string;
|
|
name: string;
|
|
assignedPartyId: string | null;
|
|
roleId: string;
|
|
type: 'signature' | 'initials' | 'date' | 'text';
|
|
required: boolean;
|
|
status: 'pending' | 'ready' | 'signed' | 'declined';
|
|
signedAt?: number;
|
|
signatureData?: any;
|
|
position: {
|
|
paragraphId?: string;
|
|
pageNumber?: number;
|
|
x: number;
|
|
y: number;
|
|
};
|
|
}
|
|
|
|
// Signature status configuration
|
|
const SIGNATURE_STATUSES = [
|
|
{ value: 'pending', label: 'Pending', color: '#f59e0b', icon: 'lucide:Clock' },
|
|
{ value: 'ready', label: 'Ready to Sign', color: '#3b82f6', icon: 'lucide:PenTool' },
|
|
{ value: 'signed', label: 'Signed', color: '#10b981', icon: 'lucide:CheckCircle' },
|
|
{ value: 'declined', label: 'Declined', color: '#ef4444', icon: 'lucide:XCircle' },
|
|
];
|
|
|
|
const FIELD_TYPES = [
|
|
{ value: 'signature', label: 'Full Signature', icon: 'lucide:PenTool' },
|
|
{ value: 'initials', label: 'Initials', icon: 'lucide:Type' },
|
|
{ value: 'date', label: 'Date', icon: 'lucide:Calendar' },
|
|
{ value: 'text', label: 'Text Field', icon: 'lucide:TextCursor' },
|
|
];
|
|
|
|
@customElement('sdig-contract-signatures')
|
|
export class SdigContractSignatures extends DeesElement {
|
|
// ============================================================================
|
|
// STATIC
|
|
// ============================================================================
|
|
|
|
public static demo = () => html`
|
|
<sdig-contract-signatures
|
|
.contract=${plugins.sdDemodata.demoContract}
|
|
></sdig-contract-signatures>
|
|
`;
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
}
|
|
|
|
.signatures-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
|
|
/* Summary cards */
|
|
.summary-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.summary-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 20px;
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.summary-card-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.summary-card-icon.pending {
|
|
background: ${cssManager.bdTheme('#fef3c7', '#422006')};
|
|
color: ${cssManager.bdTheme('#f59e0b', '#fcd34d')};
|
|
}
|
|
|
|
.summary-card-icon.ready {
|
|
background: ${cssManager.bdTheme('#dbeafe', '#1e3a5f')};
|
|
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
|
}
|
|
|
|
.summary-card-icon.signed {
|
|
background: ${cssManager.bdTheme('#d1fae5', '#064e3b')};
|
|
color: ${cssManager.bdTheme('#10b981', '#34d399')};
|
|
}
|
|
|
|
.summary-card-icon.declined {
|
|
background: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
|
color: ${cssManager.bdTheme('#ef4444', '#f87171')};
|
|
}
|
|
|
|
.summary-card-value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
}
|
|
|
|
.summary-card-label {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
/* 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-content {
|
|
padding: 20px;
|
|
}
|
|
|
|
/* Signature fields list */
|
|
.fields-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.field-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 16px;
|
|
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
border-radius: 10px;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.field-card:hover {
|
|
border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
|
|
}
|
|
|
|
.field-card.selected {
|
|
border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
|
background: ${cssManager.bdTheme('#eff6ff', '#172554')};
|
|
}
|
|
|
|
.field-icon {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.field-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.field-name {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.field-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.field-meta-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.field-meta-item dees-icon {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.field-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 6px 12px;
|
|
border-radius: 9999px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.field-status.pending {
|
|
background: ${cssManager.bdTheme('#fef3c7', '#422006')};
|
|
color: ${cssManager.bdTheme('#92400e', '#fcd34d')};
|
|
}
|
|
|
|
.field-status.ready {
|
|
background: ${cssManager.bdTheme('#dbeafe', '#1e3a5f')};
|
|
color: ${cssManager.bdTheme('#1e40af', '#93c5fd')};
|
|
}
|
|
|
|
.field-status.signed {
|
|
background: ${cssManager.bdTheme('#d1fae5', '#064e3b')};
|
|
color: ${cssManager.bdTheme('#065f46', '#6ee7b7')};
|
|
}
|
|
|
|
.field-status.declined {
|
|
background: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
|
color: ${cssManager.bdTheme('#991b1b', '#fca5a5')};
|
|
}
|
|
|
|
.field-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Signer progress */
|
|
.signers-section {
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.signers-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.signer-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 16px;
|
|
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.signer-avatar {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: white;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.signer-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.signer-name {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.signer-role {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.signer-progress {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.progress-bar {
|
|
flex: 1;
|
|
height: 6px;
|
|
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: ${cssManager.bdTheme('#10b981', '#34d399')};
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.progress-text {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
min-width: 36px;
|
|
text-align: right;
|
|
}
|
|
|
|
/* Signature preview */
|
|
.signature-preview {
|
|
position: relative;
|
|
padding: 24px;
|
|
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
border-radius: 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.signature-preview-label {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.signature-preview-image {
|
|
max-width: 200px;
|
|
max-height: 80px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.signature-preview-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
|
|
}
|
|
|
|
.signature-preview-placeholder dees-icon {
|
|
font-size: 32px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* Empty state */
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 48px 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 0 20px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* 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-success {
|
|
background: ${cssManager.bdTheme('#10b981', '#059669')};
|
|
color: white;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: ${cssManager.bdTheme('#059669', '#047857')};
|
|
}
|
|
|
|
/* 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')};
|
|
}
|
|
|
|
/* Signing order */
|
|
.signing-order-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
background: ${cssManager.bdTheme('#dbeafe', '#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 selectedFieldId: string | null = null;
|
|
|
|
// Demo signature fields data
|
|
@state()
|
|
private accessor signatureFields: ISignatureField[] = [];
|
|
|
|
// ============================================================================
|
|
// LIFECYCLE
|
|
// ============================================================================
|
|
|
|
public async firstUpdated() {
|
|
// Generate demo signature fields based on contract parties
|
|
if (this.contract && this.contract.involvedParties.length > 0) {
|
|
this.signatureFields = this.contract.involvedParties.map((party, index) => ({
|
|
id: `sig-${index + 1}`,
|
|
name: `Signature - ${this.getPartyRoleName(party.role)}`,
|
|
assignedPartyId: null,
|
|
roleId: party.role,
|
|
type: 'signature' as const,
|
|
required: true,
|
|
status: index === 0 ? 'signed' : index === 1 ? 'ready' : 'pending',
|
|
signedAt: index === 0 ? Date.now() - 86400000 : undefined,
|
|
position: { x: 0, y: 0 },
|
|
}));
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EVENT HANDLERS
|
|
// ============================================================================
|
|
|
|
private handleFieldChange(path: string, value: unknown) {
|
|
this.dispatchEvent(
|
|
new CustomEvent('field-change', {
|
|
detail: { path, value },
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
private handleSelectField(fieldId: string) {
|
|
this.selectedFieldId = this.selectedFieldId === fieldId ? null : fieldId;
|
|
}
|
|
|
|
private handleAddField() {
|
|
const newField: ISignatureField = {
|
|
id: `sig-${Date.now()}`,
|
|
name: 'New Signature Field',
|
|
assignedPartyId: null,
|
|
roleId: '',
|
|
type: 'signature',
|
|
required: true,
|
|
status: 'pending',
|
|
position: { x: 0, y: 0 },
|
|
};
|
|
this.signatureFields = [...this.signatureFields, newField];
|
|
}
|
|
|
|
private handleDeleteField(fieldId: string) {
|
|
this.signatureFields = this.signatureFields.filter((f) => f.id !== fieldId);
|
|
if (this.selectedFieldId === fieldId) {
|
|
this.selectedFieldId = null;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// HELPERS
|
|
// ============================================================================
|
|
|
|
private getPartyRoleName(roleId: string): string {
|
|
const role = this.contract?.availableRoles.find((r) => r.id === roleId);
|
|
return role?.name || roleId;
|
|
}
|
|
|
|
private getStatusConfig(status: string) {
|
|
return SIGNATURE_STATUSES.find((s) => s.value === status) || SIGNATURE_STATUSES[0];
|
|
}
|
|
|
|
private getFieldTypeConfig(type: string) {
|
|
return FIELD_TYPES.find((t) => t.value === type) || FIELD_TYPES[0];
|
|
}
|
|
|
|
private getSignatureStats() {
|
|
const total = this.signatureFields.length;
|
|
const signed = this.signatureFields.filter((f) => f.status === 'signed').length;
|
|
const ready = this.signatureFields.filter((f) => f.status === 'ready').length;
|
|
const pending = this.signatureFields.filter((f) => f.status === 'pending').length;
|
|
const declined = this.signatureFields.filter((f) => f.status === 'declined').length;
|
|
return { total, signed, ready, pending, declined };
|
|
}
|
|
|
|
private getPartyColor(index: number): string {
|
|
const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899'];
|
|
return colors[index % colors.length];
|
|
}
|
|
|
|
private formatDate(timestamp: number): string {
|
|
return new Date(timestamp).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
}
|
|
|
|
// ============================================================================
|
|
// RENDER
|
|
// ============================================================================
|
|
|
|
public render(): TemplateResult {
|
|
if (!this.contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
const stats = this.getSignatureStats();
|
|
|
|
return html`
|
|
<div class="signatures-container">
|
|
<!-- Summary Cards -->
|
|
<div class="summary-row">
|
|
<div class="summary-card">
|
|
<div class="summary-card-icon pending">
|
|
<dees-icon .icon=${'lucide:Clock'}></dees-icon>
|
|
</div>
|
|
<div class="summary-card-value">${stats.pending}</div>
|
|
<div class="summary-card-label">Pending</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="summary-card-icon ready">
|
|
<dees-icon .icon=${'lucide:PenTool'}></dees-icon>
|
|
</div>
|
|
<div class="summary-card-value">${stats.ready}</div>
|
|
<div class="summary-card-label">Ready to Sign</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="summary-card-icon signed">
|
|
<dees-icon .icon=${'lucide:CheckCircle'}></dees-icon>
|
|
</div>
|
|
<div class="summary-card-value">${stats.signed}</div>
|
|
<div class="summary-card-label">Signed</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="summary-card-icon declined">
|
|
<dees-icon .icon=${'lucide:XCircle'}></dees-icon>
|
|
</div>
|
|
<div class="summary-card-value">${stats.declined}</div>
|
|
<div class="summary-card-label">Declined</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Signature Fields Section -->
|
|
<div class="section-card">
|
|
<div class="section-header">
|
|
<div class="section-title">
|
|
<dees-icon .icon=${'lucide:PenTool'}></dees-icon>
|
|
Signature Fields
|
|
</div>
|
|
${!this.readonly
|
|
? html`
|
|
<button class="btn btn-primary" @click=${this.handleAddField}>
|
|
<dees-icon .icon=${'lucide:Plus'}></dees-icon>
|
|
Add Field
|
|
</button>
|
|
`
|
|
: ''}
|
|
</div>
|
|
<div class="section-content">
|
|
${this.signatureFields.length > 0
|
|
? html`
|
|
<div class="fields-list">
|
|
${this.signatureFields.map((field, index) => this.renderSignatureField(field, index))}
|
|
</div>
|
|
`
|
|
: html`
|
|
<div class="empty-state">
|
|
<dees-icon .icon=${'lucide:PenTool'}></dees-icon>
|
|
<h4>No Signature Fields</h4>
|
|
<p>Add signature fields to define where parties should sign the contract</p>
|
|
${!this.readonly
|
|
? html`
|
|
<button class="btn btn-primary" @click=${this.handleAddField}>
|
|
<dees-icon .icon=${'lucide:Plus'}></dees-icon>
|
|
Add Signature Field
|
|
</button>
|
|
`
|
|
: ''}
|
|
</div>
|
|
`}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Signers Progress Section -->
|
|
${this.contract.involvedParties.length > 0
|
|
? html`
|
|
<div class="section-card">
|
|
<div class="section-header">
|
|
<div class="section-title">
|
|
<dees-icon .icon=${'lucide:Users'}></dees-icon>
|
|
Signers Progress
|
|
</div>
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="signers-grid">
|
|
${this.contract.involvedParties.map((party, index) => this.renderSignerCard(party, index))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
: ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderSignatureField(field: ISignatureField, index: number): TemplateResult {
|
|
const isSelected = this.selectedFieldId === field.id;
|
|
const statusConfig = this.getStatusConfig(field.status);
|
|
const typeConfig = this.getFieldTypeConfig(field.type);
|
|
|
|
return html`
|
|
<div
|
|
class="field-card ${isSelected ? 'selected' : ''}"
|
|
@click=${() => this.handleSelectField(field.id)}
|
|
>
|
|
<div class="signing-order-badge">${index + 1}</div>
|
|
|
|
<div class="field-icon">
|
|
<dees-icon .icon=${typeConfig.icon}></dees-icon>
|
|
</div>
|
|
|
|
<div class="field-info">
|
|
<div class="field-name">${field.name}</div>
|
|
<div class="field-meta">
|
|
<span class="field-meta-item">
|
|
<dees-icon .icon=${'lucide:User'}></dees-icon>
|
|
${this.getPartyRoleName(field.roleId)}
|
|
</span>
|
|
<span class="type-badge">
|
|
<dees-icon .icon=${typeConfig.icon}></dees-icon>
|
|
${typeConfig.label}
|
|
</span>
|
|
${field.required
|
|
? html`
|
|
<span class="field-meta-item">
|
|
<dees-icon .icon=${'lucide:Asterisk'}></dees-icon>
|
|
Required
|
|
</span>
|
|
`
|
|
: ''}
|
|
${field.signedAt
|
|
? html`
|
|
<span class="field-meta-item">
|
|
<dees-icon .icon=${'lucide:Calendar'}></dees-icon>
|
|
${this.formatDate(field.signedAt)}
|
|
</span>
|
|
`
|
|
: ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field-status ${field.status}">
|
|
<dees-icon .icon=${statusConfig.icon}></dees-icon>
|
|
${statusConfig.label}
|
|
</div>
|
|
|
|
${!this.readonly
|
|
? html`
|
|
<div class="field-actions">
|
|
<button class="btn btn-ghost" @click=${(e: Event) => { e.stopPropagation(); }} title="Edit">
|
|
<dees-icon .icon=${'lucide:Pencil'}></dees-icon>
|
|
</button>
|
|
<button
|
|
class="btn btn-ghost"
|
|
@click=${(e: Event) => { e.stopPropagation(); this.handleDeleteField(field.id); }}
|
|
title="Delete"
|
|
style="color: #ef4444;"
|
|
>
|
|
<dees-icon .icon=${'lucide:Trash2'}></dees-icon>
|
|
</button>
|
|
</div>
|
|
`
|
|
: ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderSignerCard(party: plugins.sdInterfaces.IInvolvedParty, index: number): TemplateResult {
|
|
const partyFields = this.signatureFields.filter((f) => f.roleId === party.role);
|
|
const signedFields = partyFields.filter((f) => f.status === 'signed').length;
|
|
const totalFields = partyFields.length;
|
|
const progress = totalFields > 0 ? Math.round((signedFields / totalFields) * 100) : 0;
|
|
const roleName = this.getPartyRoleName(party.role);
|
|
|
|
return html`
|
|
<div class="signer-card">
|
|
<div class="signer-avatar" style="background: ${this.getPartyColor(index)}">
|
|
${roleName.charAt(0).toUpperCase()}
|
|
</div>
|
|
<div class="signer-info">
|
|
<div class="signer-name">${roleName}</div>
|
|
<div class="signer-role">${signedFields} of ${totalFields} signatures</div>
|
|
<div class="signer-progress">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${progress}%"></div>
|
|
</div>
|
|
<span class="progress-text">${progress}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|