feat(catalog): add ContractEditor and many editor subcomponents; implement SignPad and SignBox; update README and bump dependencies
This commit is contained in:
873
ts_web/elements/sdig-contract-terms/sdig-contract-terms.ts
Normal file
873
ts_web/elements/sdig-contract-terms/sdig-contract-terms.ts
Normal file
@@ -0,0 +1,873 @@
|
||||
/**
|
||||
* @file sdig-contract-terms.ts
|
||||
* @description Contract terms editor - tabbed container for financial, time, and obligation terms
|
||||
*/
|
||||
|
||||
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-terms': SdigContractTerms;
|
||||
}
|
||||
}
|
||||
|
||||
// Term types
|
||||
type TTermTab = 'financial' | 'time' | 'obligations';
|
||||
|
||||
interface ITermTabConfig {
|
||||
id: TTermTab;
|
||||
label: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const TERM_TABS: ITermTabConfig[] = [
|
||||
{ id: 'financial', label: 'Financial Terms', icon: 'lucide:banknote', description: 'Payment schedules, rates, and penalties' },
|
||||
{ id: 'time', label: 'Time Terms', icon: 'lucide:calendar', description: 'Milestones, deadlines, and renewal' },
|
||||
{ id: 'obligations', label: 'Obligations', icon: 'lucide:check-square', description: 'Deliverables, SLAs, and warranties' },
|
||||
];
|
||||
|
||||
// Extended contract terms interfaces (for future interface updates)
|
||||
interface IPaymentScheduleItem {
|
||||
id: string;
|
||||
description: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
dueDate: string;
|
||||
status: 'pending' | 'paid' | 'overdue';
|
||||
}
|
||||
|
||||
interface IMilestone {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
dueDate: string;
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'delayed';
|
||||
dependencies: string[];
|
||||
}
|
||||
|
||||
interface IObligation {
|
||||
id: string;
|
||||
description: string;
|
||||
responsibleParty: string;
|
||||
deadline: string;
|
||||
status: 'pending' | 'completed' | 'waived';
|
||||
}
|
||||
|
||||
@customElement('sdig-contract-terms')
|
||||
export class SdigContractTerms extends DeesElement {
|
||||
// ============================================================================
|
||||
// STATIC
|
||||
// ============================================================================
|
||||
|
||||
public static demo = () => html`
|
||||
<sdig-contract-terms
|
||||
.contract=${plugins.sdDemodata.demoContract}
|
||||
></sdig-contract-terms>
|
||||
`;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.terms-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* Section card */
|
||||
.section-card {
|
||||
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Tab navigation */
|
||||
.tabs-nav {
|
||||
display: flex;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px 24px;
|
||||
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;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
||||
background: ${cssManager.bdTheme('#f3f4f6', '#18181b')};
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
border-bottom-color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
||||
}
|
||||
|
||||
.tab-btn dees-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Tab content */
|
||||
.tab-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Sub-sections */
|
||||
.sub-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.sub-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.sub-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sub-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
}
|
||||
|
||||
.sub-section-description {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Form groups */
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
||||
}
|
||||
|
||||
.form-input {
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
||||
border: 1px solid ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(96, 165, 250, 0.1)')};
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
|
||||
}
|
||||
|
||||
select.form-input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Data table */
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 12px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
padding: 12px 16px;
|
||||
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
}
|
||||
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.data-table tr:hover td {
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
}
|
||||
|
||||
/* Status badges */
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 9999px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
background: ${cssManager.bdTheme('#fef3c7', '#422006')};
|
||||
color: ${cssManager.bdTheme('#92400e', '#fcd34d')};
|
||||
}
|
||||
|
||||
.status-badge.paid,
|
||||
.status-badge.completed {
|
||||
background: ${cssManager.bdTheme('#d1fae5', '#064e3b')};
|
||||
color: ${cssManager.bdTheme('#065f46', '#6ee7b7')};
|
||||
}
|
||||
|
||||
.status-badge.overdue,
|
||||
.status-badge.delayed {
|
||||
background: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
||||
color: ${cssManager.bdTheme('#991b1b', '#fca5a5')};
|
||||
}
|
||||
|
||||
.status-badge.in_progress {
|
||||
background: ${cssManager.bdTheme('#dbeafe', '#1e3a5f')};
|
||||
color: ${cssManager.bdTheme('#1e40af', '#93c5fd')};
|
||||
}
|
||||
|
||||
/* Amount display */
|
||||
.amount {
|
||||
font-weight: 600;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
|
||||
.amount.positive {
|
||||
color: ${cssManager.bdTheme('#059669', '#34d399')};
|
||||
}
|
||||
|
||||
.amount.negative {
|
||||
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||
}
|
||||
|
||||
/* Summary card */
|
||||
.summary-card {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
padding: 20px;
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
}
|
||||
|
||||
.summary-value.currency {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
|
||||
/* 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')};
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
|
||||
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
||||
}
|
||||
|
||||
/* Add row button */
|
||||
.add-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
border-top: 1px dashed ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
||||
}
|
||||
|
||||
/* Info banner */
|
||||
.info-banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: ${cssManager.bdTheme('#eff6ff', '#172554')};
|
||||
border: 1px solid ${cssManager.bdTheme('#bfdbfe', '#1e40af')};
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.info-banner dees-icon {
|
||||
font-size: 20px;
|
||||
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-banner-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-banner-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#1e40af', '#93c5fd')};
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.info-banner-text {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// PROPERTIES
|
||||
// ============================================================================
|
||||
|
||||
@property({ type: Object })
|
||||
public accessor contract: plugins.sdInterfaces.IPortableContract | null = null;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public accessor readonly: boolean = false;
|
||||
|
||||
// ============================================================================
|
||||
// STATE
|
||||
// ============================================================================
|
||||
|
||||
@state()
|
||||
private accessor activeTab: TTermTab = 'financial';
|
||||
|
||||
// Demo data for terms (will be replaced with actual contract data when interface is extended)
|
||||
@state()
|
||||
private accessor paymentSchedule: IPaymentScheduleItem[] = [
|
||||
{ id: '1', description: 'Initial deposit', amount: 5000, currency: 'EUR', dueDate: '2024-02-01', status: 'paid' },
|
||||
{ id: '2', description: 'Monthly payment - March', amount: 1000, currency: 'EUR', dueDate: '2024-03-01', status: 'paid' },
|
||||
{ id: '3', description: 'Monthly payment - April', amount: 1000, currency: 'EUR', dueDate: '2024-04-01', status: 'pending' },
|
||||
];
|
||||
|
||||
@state()
|
||||
private accessor milestones: IMilestone[] = [
|
||||
{ id: '1', name: 'Project Kickoff', description: 'Initial planning and setup', dueDate: '2024-02-15', status: 'completed', dependencies: [] },
|
||||
{ id: '2', name: 'Phase 1 Delivery', description: 'First deliverable milestone', dueDate: '2024-03-15', status: 'in_progress', dependencies: ['1'] },
|
||||
{ id: '3', name: 'Final Delivery', description: 'Complete project delivery', dueDate: '2024-05-01', status: 'pending', dependencies: ['2'] },
|
||||
];
|
||||
|
||||
@state()
|
||||
private accessor obligations: IObligation[] = [
|
||||
{ id: '1', description: 'Provide access credentials', responsibleParty: 'employer', deadline: '2024-02-01', status: 'completed' },
|
||||
{ id: '2', description: 'Submit monthly reports', responsibleParty: 'employee', deadline: '2024-03-01', status: 'pending' },
|
||||
{ id: '3', description: 'Conduct quarterly review', responsibleParty: 'employer', deadline: '2024-04-01', status: 'pending' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================================
|
||||
|
||||
private handleFieldChange(path: string, value: unknown) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('field-change', {
|
||||
detail: { path, value },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private handleTabChange(tab: TTermTab) {
|
||||
this.activeTab = tab;
|
||||
}
|
||||
|
||||
private handleAddPayment() {
|
||||
const newPayment: IPaymentScheduleItem = {
|
||||
id: `pay-${Date.now()}`,
|
||||
description: 'New payment',
|
||||
amount: 0,
|
||||
currency: 'EUR',
|
||||
dueDate: new Date().toISOString().split('T')[0],
|
||||
status: 'pending',
|
||||
};
|
||||
this.paymentSchedule = [...this.paymentSchedule, newPayment];
|
||||
}
|
||||
|
||||
private handleAddMilestone() {
|
||||
const newMilestone: IMilestone = {
|
||||
id: `ms-${Date.now()}`,
|
||||
name: 'New Milestone',
|
||||
description: '',
|
||||
dueDate: new Date().toISOString().split('T')[0],
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
};
|
||||
this.milestones = [...this.milestones, newMilestone];
|
||||
}
|
||||
|
||||
private handleAddObligation() {
|
||||
const newObligation: IObligation = {
|
||||
id: `obl-${Date.now()}`,
|
||||
description: 'New obligation',
|
||||
responsibleParty: '',
|
||||
deadline: new Date().toISOString().split('T')[0],
|
||||
status: 'pending',
|
||||
};
|
||||
this.obligations = [...this.obligations, newObligation];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPERS
|
||||
// ============================================================================
|
||||
|
||||
private formatCurrency(amount: number, currency: string): string {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
private formatDate(dateStr: string): string {
|
||||
return new Date(dateStr).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
private getTotalAmount(): number {
|
||||
return this.paymentSchedule.reduce((sum, p) => sum + p.amount, 0);
|
||||
}
|
||||
|
||||
private getPaidAmount(): number {
|
||||
return this.paymentSchedule.filter((p) => p.status === 'paid').reduce((sum, p) => sum + p.amount, 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>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="terms-container">
|
||||
<div class="section-card">
|
||||
<!-- Tabs Navigation -->
|
||||
<nav class="tabs-nav">
|
||||
${TERM_TABS.map(
|
||||
(tab) => html`
|
||||
<button
|
||||
class="tab-btn ${this.activeTab === tab.id ? 'active' : ''}"
|
||||
@click=${() => this.handleTabChange(tab.id)}
|
||||
>
|
||||
<dees-icon .iconFA=${tab.icon}></dees-icon>
|
||||
${tab.label}
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content">
|
||||
${this.activeTab === 'financial'
|
||||
? this.renderFinancialTerms()
|
||||
: this.activeTab === 'time'
|
||||
? this.renderTimeTerms()
|
||||
: this.renderObligations()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderFinancialTerms(): TemplateResult {
|
||||
const totalAmount = this.getTotalAmount();
|
||||
const paidAmount = this.getPaidAmount();
|
||||
const pendingAmount = totalAmount - paidAmount;
|
||||
|
||||
return html`
|
||||
<!-- Summary -->
|
||||
<div class="summary-card">
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Total Value</span>
|
||||
<span class="summary-value currency">${this.formatCurrency(totalAmount, 'EUR')}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Paid</span>
|
||||
<span class="summary-value currency" style="color: #059669;">${this.formatCurrency(paidAmount, 'EUR')}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Pending</span>
|
||||
<span class="summary-value currency" style="color: #f59e0b;">${this.formatCurrency(pendingAmount, 'EUR')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Schedule -->
|
||||
<div class="sub-section">
|
||||
<div class="sub-section-header">
|
||||
<div>
|
||||
<div class="sub-section-title">Payment Schedule</div>
|
||||
<div class="sub-section-description">Scheduled payments and their status</div>
|
||||
</div>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<button class="btn btn-secondary" @click=${this.handleAddPayment}>
|
||||
<dees-icon .iconFA=${'lucide:plus'}></dees-icon>
|
||||
Add Payment
|
||||
</button>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
|
||||
${this.paymentSchedule.length > 0
|
||||
? html`
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Amount</th>
|
||||
<th>Due Date</th>
|
||||
<th>Status</th>
|
||||
${!this.readonly ? html`<th></th>` : ''}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${this.paymentSchedule.map(
|
||||
(payment) => html`
|
||||
<tr>
|
||||
<td>${payment.description}</td>
|
||||
<td><span class="amount">${this.formatCurrency(payment.amount, payment.currency)}</span></td>
|
||||
<td>${this.formatDate(payment.dueDate)}</td>
|
||||
<td><span class="status-badge ${payment.status}">${payment.status}</span></td>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<td>
|
||||
<button class="btn btn-ghost btn-sm">
|
||||
<dees-icon .iconFA=${'lucide:pencil'}></dees-icon>
|
||||
</button>
|
||||
</td>
|
||||
`
|
||||
: ''}
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
`
|
||||
: html`
|
||||
<div class="empty-state">
|
||||
<dees-icon .iconFA=${'lucide:banknote'}></dees-icon>
|
||||
<h4>No Payment Schedule</h4>
|
||||
<p>Add payment terms to track financial obligations</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderTimeTerms(): TemplateResult {
|
||||
const completedCount = this.milestones.filter((m) => m.status === 'completed').length;
|
||||
const totalCount = this.milestones.length;
|
||||
|
||||
return html`
|
||||
<!-- Summary -->
|
||||
<div class="summary-card">
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Total Milestones</span>
|
||||
<span class="summary-value">${totalCount}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Completed</span>
|
||||
<span class="summary-value" style="color: #059669;">${completedCount}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Progress</span>
|
||||
<span class="summary-value">${totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Milestones -->
|
||||
<div class="sub-section">
|
||||
<div class="sub-section-header">
|
||||
<div>
|
||||
<div class="sub-section-title">Milestones</div>
|
||||
<div class="sub-section-description">Key project milestones and deadlines</div>
|
||||
</div>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<button class="btn btn-secondary" @click=${this.handleAddMilestone}>
|
||||
<dees-icon .iconFA=${'lucide:plus'}></dees-icon>
|
||||
Add Milestone
|
||||
</button>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
|
||||
${this.milestones.length > 0
|
||||
? html`
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Milestone</th>
|
||||
<th>Description</th>
|
||||
<th>Due Date</th>
|
||||
<th>Status</th>
|
||||
${!this.readonly ? html`<th></th>` : ''}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${this.milestones.map(
|
||||
(milestone) => html`
|
||||
<tr>
|
||||
<td><strong>${milestone.name}</strong></td>
|
||||
<td>${milestone.description || '—'}</td>
|
||||
<td>${this.formatDate(milestone.dueDate)}</td>
|
||||
<td><span class="status-badge ${milestone.status}">${milestone.status.replace('_', ' ')}</span></td>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<td>
|
||||
<button class="btn btn-ghost btn-sm">
|
||||
<dees-icon .iconFA=${'lucide:pencil'}></dees-icon>
|
||||
</button>
|
||||
</td>
|
||||
`
|
||||
: ''}
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
`
|
||||
: html`
|
||||
<div class="empty-state">
|
||||
<dees-icon .iconFA=${'lucide:calendar'}></dees-icon>
|
||||
<h4>No Milestones</h4>
|
||||
<p>Add milestones to track project progress</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderObligations(): TemplateResult {
|
||||
const completedCount = this.obligations.filter((o) => o.status === 'completed').length;
|
||||
|
||||
return html`
|
||||
<!-- Summary -->
|
||||
<div class="summary-card">
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Total Obligations</span>
|
||||
<span class="summary-value">${this.obligations.length}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Completed</span>
|
||||
<span class="summary-value" style="color: #059669;">${completedCount}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="summary-label">Pending</span>
|
||||
<span class="summary-value" style="color: #f59e0b;">${this.obligations.length - completedCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info banner -->
|
||||
<div class="info-banner">
|
||||
<dees-icon .iconFA=${'lucide:info'}></dees-icon>
|
||||
<div class="info-banner-content">
|
||||
<div class="info-banner-title">Contractual Obligations</div>
|
||||
<div class="info-banner-text">
|
||||
Track responsibilities assigned to each party. Mark obligations as completed when fulfilled.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Obligations -->
|
||||
<div class="sub-section">
|
||||
<div class="sub-section-header">
|
||||
<div>
|
||||
<div class="sub-section-title">Party Obligations</div>
|
||||
<div class="sub-section-description">Responsibilities and deliverables by party</div>
|
||||
</div>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<button class="btn btn-secondary" @click=${this.handleAddObligation}>
|
||||
<dees-icon .iconFA=${'lucide:plus'}></dees-icon>
|
||||
Add Obligation
|
||||
</button>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
|
||||
${this.obligations.length > 0
|
||||
? html`
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Obligation</th>
|
||||
<th>Responsible Party</th>
|
||||
<th>Deadline</th>
|
||||
<th>Status</th>
|
||||
${!this.readonly ? html`<th></th>` : ''}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${this.obligations.map(
|
||||
(obligation) => html`
|
||||
<tr>
|
||||
<td>${obligation.description}</td>
|
||||
<td>${this.getPartyName(obligation.responsibleParty)}</td>
|
||||
<td>${this.formatDate(obligation.deadline)}</td>
|
||||
<td><span class="status-badge ${obligation.status}">${obligation.status}</span></td>
|
||||
${!this.readonly
|
||||
? html`
|
||||
<td>
|
||||
<button class="btn btn-ghost btn-sm">
|
||||
<dees-icon .iconFA=${'lucide:pencil'}></dees-icon>
|
||||
</button>
|
||||
</td>
|
||||
`
|
||||
: ''}
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
`
|
||||
: html`
|
||||
<div class="empty-state">
|
||||
<dees-icon .iconFA=${'lucide:check-square'}></dees-icon>
|
||||
<h4>No Obligations</h4>
|
||||
<p>Add obligations to track party responsibilities</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user