874 lines
26 KiB
TypeScript
874 lines
26 KiB
TypeScript
/**
|
|
* @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>
|
|
`;
|
|
}
|
|
}
|