773 lines
22 KiB
TypeScript
773 lines
22 KiB
TypeScript
/**
|
|
* @file sdig-contract-audit.ts
|
|
* @description Contract audit log and lifecycle history 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-audit': SdigContractAudit;
|
|
}
|
|
}
|
|
|
|
// Audit event interface
|
|
interface IAuditEvent {
|
|
id: string;
|
|
timestamp: number;
|
|
type: 'created' | 'updated' | 'status_change' | 'signature' | 'comment' | 'attachment' | 'viewed' | 'shared';
|
|
userId: string;
|
|
userName: string;
|
|
userColor: string;
|
|
description: string;
|
|
details?: {
|
|
field?: string;
|
|
oldValue?: string;
|
|
newValue?: string;
|
|
attachmentName?: string;
|
|
signatureStatus?: string;
|
|
};
|
|
}
|
|
|
|
// Status workflow configuration
|
|
const STATUS_WORKFLOW = [
|
|
{ id: 'draft', label: 'Draft', icon: 'lucide:FileEdit', color: '#f59e0b' },
|
|
{ id: 'review', label: 'Review', icon: 'lucide:Eye', color: '#3b82f6' },
|
|
{ id: 'pending', label: 'Pending Signatures', icon: 'lucide:PenTool', color: '#8b5cf6' },
|
|
{ id: 'signed', label: 'Signed', icon: 'lucide:CheckCircle', color: '#10b981' },
|
|
{ id: 'executed', label: 'Executed', icon: 'lucide:ShieldCheck', color: '#059669' },
|
|
];
|
|
|
|
// Event type configuration
|
|
const EVENT_TYPES = {
|
|
created: { icon: 'lucide:PlusCircle', color: '#10b981', label: 'Created' },
|
|
updated: { icon: 'lucide:Pencil', color: '#3b82f6', label: 'Updated' },
|
|
status_change: { icon: 'lucide:ArrowRightCircle', color: '#8b5cf6', label: 'Status Changed' },
|
|
signature: { icon: 'lucide:PenTool', color: '#10b981', label: 'Signature' },
|
|
comment: { icon: 'lucide:MessageCircle', color: '#f59e0b', label: 'Comment' },
|
|
attachment: { icon: 'lucide:Paperclip', color: '#6366f1', label: 'Attachment' },
|
|
viewed: { icon: 'lucide:Eye', color: '#6b7280', label: 'Viewed' },
|
|
shared: { icon: 'lucide:Share2', color: '#ec4899', label: 'Shared' },
|
|
};
|
|
|
|
@customElement('sdig-contract-audit')
|
|
export class SdigContractAudit extends DeesElement {
|
|
// ============================================================================
|
|
// STATIC
|
|
// ============================================================================
|
|
|
|
public static demo = () => html`
|
|
<sdig-contract-audit
|
|
.contract=${plugins.sdDemodata.demoContract}
|
|
></sdig-contract-audit>
|
|
`;
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
}
|
|
|
|
.audit-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
|
|
/* Lifecycle status */
|
|
.lifecycle-card {
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
}
|
|
|
|
.lifecycle-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.status-workflow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
overflow-x: auto;
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.status-step {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-width: 100px;
|
|
}
|
|
|
|
.status-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 22px;
|
|
background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
|
|
color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.status-step.completed .status-icon {
|
|
background: ${cssManager.bdTheme('#d1fae5', '#064e3b')};
|
|
color: ${cssManager.bdTheme('#059669', '#34d399')};
|
|
}
|
|
|
|
.status-step.current .status-icon {
|
|
background: ${cssManager.bdTheme('#dbeafe', '#1e3a5f')};
|
|
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
|
box-shadow: 0 0 0 4px ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(96, 165, 250, 0.2)')};
|
|
}
|
|
|
|
.status-label {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
text-align: center;
|
|
}
|
|
|
|
.status-step.completed .status-label,
|
|
.status-step.current .status-label {
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
}
|
|
|
|
.status-connector {
|
|
flex: 1;
|
|
height: 2px;
|
|
background: ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
min-width: 40px;
|
|
}
|
|
|
|
.status-connector.completed {
|
|
background: ${cssManager.bdTheme('#10b981', '#34d399')};
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Filter controls */
|
|
.filter-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.filter-select {
|
|
padding: 8px 12px;
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border: 1px solid ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
|
|
border-radius: 6px;
|
|
outline: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.search-input {
|
|
flex: 1;
|
|
padding: 8px 12px;
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border: 1px solid ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
|
|
border-radius: 6px;
|
|
outline: none;
|
|
}
|
|
|
|
.search-input:focus {
|
|
border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
|
}
|
|
|
|
/* Timeline */
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 32px;
|
|
}
|
|
|
|
.timeline::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 11px;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background: ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-bottom: 24px;
|
|
}
|
|
|
|
.timeline-item:last-child {
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.timeline-dot {
|
|
position: absolute;
|
|
left: -32px;
|
|
top: 0;
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border: 2px solid;
|
|
}
|
|
|
|
.timeline-content {
|
|
padding: 16px;
|
|
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.timeline-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.timeline-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
}
|
|
|
|
.timeline-time {
|
|
font-size: 12px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.timeline-user {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.timeline-avatar {
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: white;
|
|
}
|
|
|
|
.timeline-username {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
}
|
|
|
|
.timeline-description {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.timeline-details {
|
|
margin-top: 10px;
|
|
padding: 10px;
|
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
font-family: 'Roboto Mono', monospace;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.detail-row:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.detail-label {
|
|
font-weight: 500;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
.detail-value {
|
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
}
|
|
|
|
.detail-old {
|
|
text-decoration: line-through;
|
|
color: ${cssManager.bdTheme('#ef4444', '#f87171')};
|
|
}
|
|
|
|
.detail-new {
|
|
color: ${cssManager.bdTheme('#10b981', '#34d399')};
|
|
}
|
|
|
|
/* Event type badge */
|
|
.event-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Stats row */
|
|
.stats-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.stat-card {
|
|
padding: 16px;
|
|
background: ${cssManager.bdTheme('#f9fafb', '#111111')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
|
|
border-radius: 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: ${cssManager.bdTheme('#111111', '#fafafa')};
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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-secondary {
|
|
background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
|
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
|
|
}
|
|
`,
|
|
];
|
|
|
|
// ============================================================================
|
|
// PROPERTIES
|
|
// ============================================================================
|
|
|
|
@property({ type: Object })
|
|
public accessor contract: plugins.sdInterfaces.IPortableContract | null = null;
|
|
|
|
// ============================================================================
|
|
// STATE
|
|
// ============================================================================
|
|
|
|
@state()
|
|
private accessor filterType: string = 'all';
|
|
|
|
@state()
|
|
private accessor searchQuery: string = '';
|
|
|
|
// Demo audit events
|
|
@state()
|
|
private accessor auditEvents: IAuditEvent[] = [
|
|
{
|
|
id: '1',
|
|
timestamp: Date.now() - 3600000,
|
|
type: 'signature',
|
|
userId: '1',
|
|
userName: 'Alice Smith',
|
|
userColor: '#3b82f6',
|
|
description: 'Signed the contract',
|
|
details: { signatureStatus: 'completed' },
|
|
},
|
|
{
|
|
id: '2',
|
|
timestamp: Date.now() - 7200000,
|
|
type: 'status_change',
|
|
userId: '2',
|
|
userName: 'Bob Johnson',
|
|
userColor: '#10b981',
|
|
description: 'Changed status from Review to Pending Signatures',
|
|
details: { field: 'status', oldValue: 'review', newValue: 'pending' },
|
|
},
|
|
{
|
|
id: '3',
|
|
timestamp: Date.now() - 86400000,
|
|
type: 'updated',
|
|
userId: '2',
|
|
userName: 'Bob Johnson',
|
|
userColor: '#10b981',
|
|
description: 'Updated compensation amount',
|
|
details: { field: 'paragraphs.2.content', oldValue: '[Salary Amount]', newValue: '€520/month' },
|
|
},
|
|
{
|
|
id: '4',
|
|
timestamp: Date.now() - 86400000 * 2,
|
|
type: 'comment',
|
|
userId: '1',
|
|
userName: 'Alice Smith',
|
|
userColor: '#3b82f6',
|
|
description: 'Added a comment on Compensation section',
|
|
},
|
|
{
|
|
id: '5',
|
|
timestamp: Date.now() - 86400000 * 3,
|
|
type: 'attachment',
|
|
userId: '3',
|
|
userName: 'Carol Davis',
|
|
userColor: '#f59e0b',
|
|
description: 'Uploaded ID verification document',
|
|
details: { attachmentName: 'ID_Verification.pdf' },
|
|
},
|
|
{
|
|
id: '6',
|
|
timestamp: Date.now() - 86400000 * 5,
|
|
type: 'created',
|
|
userId: '2',
|
|
userName: 'Bob Johnson',
|
|
userColor: '#10b981',
|
|
description: 'Created the contract',
|
|
},
|
|
];
|
|
|
|
// ============================================================================
|
|
// HELPERS
|
|
// ============================================================================
|
|
|
|
private getEventConfig(type: IAuditEvent['type']) {
|
|
return EVENT_TYPES[type] || EVENT_TYPES.updated;
|
|
}
|
|
|
|
private getFilteredEvents(): IAuditEvent[] {
|
|
let events = this.auditEvents;
|
|
|
|
if (this.filterType !== 'all') {
|
|
events = events.filter((e) => e.type === this.filterType);
|
|
}
|
|
|
|
if (this.searchQuery) {
|
|
const query = this.searchQuery.toLowerCase();
|
|
events = events.filter(
|
|
(e) =>
|
|
e.description.toLowerCase().includes(query) ||
|
|
e.userName.toLowerCase().includes(query)
|
|
);
|
|
}
|
|
|
|
return events;
|
|
}
|
|
|
|
private formatDate(timestamp: number): string {
|
|
return new Date(timestamp).toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
}
|
|
|
|
private formatTimeAgo(timestamp: number): string {
|
|
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
if (seconds < 60) return 'just now';
|
|
const minutes = Math.floor(seconds / 60);
|
|
if (minutes < 60) return `${minutes}m ago`;
|
|
const hours = Math.floor(minutes / 60);
|
|
if (hours < 24) return `${hours}h ago`;
|
|
const days = Math.floor(hours / 24);
|
|
return `${days}d ago`;
|
|
}
|
|
|
|
private getCurrentStatusIndex(): number {
|
|
// Demo: Return a fixed position
|
|
return 2; // Pending Signatures
|
|
}
|
|
|
|
private getEventStats() {
|
|
const total = this.auditEvents.length;
|
|
const updates = this.auditEvents.filter((e) => e.type === 'updated').length;
|
|
const signatures = this.auditEvents.filter((e) => e.type === 'signature').length;
|
|
const comments = this.auditEvents.filter((e) => e.type === 'comment').length;
|
|
return { total, updates, signatures, comments };
|
|
}
|
|
|
|
// ============================================================================
|
|
// RENDER
|
|
// ============================================================================
|
|
|
|
public render(): TemplateResult {
|
|
if (!this.contract) {
|
|
return html`<div>No contract loaded</div>`;
|
|
}
|
|
|
|
const currentStatusIndex = this.getCurrentStatusIndex();
|
|
const filteredEvents = this.getFilteredEvents();
|
|
const stats = this.getEventStats();
|
|
|
|
return html`
|
|
<div class="audit-container">
|
|
<!-- Lifecycle Status -->
|
|
<div class="lifecycle-card">
|
|
<div class="lifecycle-title">Contract Lifecycle</div>
|
|
<div class="status-workflow">
|
|
${STATUS_WORKFLOW.map((status, index) => html`
|
|
<div class="status-step ${index < currentStatusIndex ? 'completed' : ''} ${index === currentStatusIndex ? 'current' : ''}">
|
|
<div class="status-icon">
|
|
<dees-icon .icon=${status.icon}></dees-icon>
|
|
</div>
|
|
<div class="status-label">${status.label}</div>
|
|
</div>
|
|
${index < STATUS_WORKFLOW.length - 1
|
|
? html`<div class="status-connector ${index < currentStatusIndex ? 'completed' : ''}"></div>`
|
|
: ''}
|
|
`)}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="stats-row">
|
|
<div class="stat-card">
|
|
<div class="stat-value">${stats.total}</div>
|
|
<div class="stat-label">Total Events</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">${stats.updates}</div>
|
|
<div class="stat-label">Updates</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">${stats.signatures}</div>
|
|
<div class="stat-label">Signatures</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">${stats.comments}</div>
|
|
<div class="stat-label">Comments</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Log -->
|
|
<div class="section-card">
|
|
<div class="section-header">
|
|
<div class="section-title">
|
|
<dees-icon .icon=${'lucide:History'}></dees-icon>
|
|
Activity Log
|
|
</div>
|
|
<button class="btn btn-secondary">
|
|
<dees-icon .icon=${'lucide:Download'}></dees-icon>
|
|
Export
|
|
</button>
|
|
</div>
|
|
<div class="section-content">
|
|
<!-- Filters -->
|
|
<div class="filter-row">
|
|
<select
|
|
class="filter-select"
|
|
.value=${this.filterType}
|
|
@change=${(e: Event) => (this.filterType = (e.target as HTMLSelectElement).value)}
|
|
>
|
|
<option value="all">All Events</option>
|
|
${Object.entries(EVENT_TYPES).map(
|
|
([key, config]) => html`<option value=${key}>${config.label}</option>`
|
|
)}
|
|
</select>
|
|
<input
|
|
type="text"
|
|
class="search-input"
|
|
placeholder="Search events..."
|
|
.value=${this.searchQuery}
|
|
@input=${(e: Event) => (this.searchQuery = (e.target as HTMLInputElement).value)}
|
|
/>
|
|
</div>
|
|
|
|
<!-- Timeline -->
|
|
${filteredEvents.length > 0
|
|
? html`
|
|
<div class="timeline">
|
|
${filteredEvents.map((event) => this.renderTimelineItem(event))}
|
|
</div>
|
|
`
|
|
: html`
|
|
<div class="empty-state">
|
|
<dees-icon .icon=${'lucide:Clock'}></dees-icon>
|
|
<h4>No Events Found</h4>
|
|
<p>No activity matches your current filters</p>
|
|
</div>
|
|
`}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderTimelineItem(event: IAuditEvent): TemplateResult {
|
|
const config = this.getEventConfig(event.type);
|
|
|
|
return html`
|
|
<div class="timeline-item">
|
|
<div class="timeline-dot" style="border-color: ${config.color}; color: ${config.color}">
|
|
<dees-icon .icon=${config.icon}></dees-icon>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="timeline-header">
|
|
<div class="timeline-title">
|
|
<span class="event-badge" style="background: ${config.color}20; color: ${config.color}">
|
|
${config.label}
|
|
</span>
|
|
</div>
|
|
<span class="timeline-time" title="${this.formatDate(event.timestamp)}">
|
|
${this.formatTimeAgo(event.timestamp)}
|
|
</span>
|
|
</div>
|
|
<div class="timeline-user">
|
|
<div class="timeline-avatar" style="background: ${event.userColor}">
|
|
${event.userName.charAt(0)}
|
|
</div>
|
|
<span class="timeline-username">${event.userName}</span>
|
|
</div>
|
|
<div class="timeline-description">${event.description}</div>
|
|
${event.details
|
|
? html`
|
|
<div class="timeline-details">
|
|
${event.details.field
|
|
? html`
|
|
<div class="detail-row">
|
|
<span class="detail-label">Field:</span>
|
|
<span class="detail-value">${event.details.field}</span>
|
|
</div>
|
|
`
|
|
: ''}
|
|
${event.details.oldValue && event.details.newValue
|
|
? html`
|
|
<div class="detail-row">
|
|
<span class="detail-old">${event.details.oldValue}</span>
|
|
<span>→</span>
|
|
<span class="detail-new">${event.details.newValue}</span>
|
|
</div>
|
|
`
|
|
: ''}
|
|
${event.details.attachmentName
|
|
? html`
|
|
<div class="detail-row">
|
|
<span class="detail-label">File:</span>
|
|
<span class="detail-value">${event.details.attachmentName}</span>
|
|
</div>
|
|
`
|
|
: ''}
|
|
</div>
|
|
`
|
|
: ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|