feat(catalog): add ContractEditor and many editor subcomponents; implement SignPad and SignBox; update README and bump dependencies
This commit is contained in:
772
ts_web/elements/sdig-contract-audit/sdig-contract-audit.ts
Normal file
772
ts_web/elements/sdig-contract-audit/sdig-contract-audit.ts
Normal file
@@ -0,0 +1,772 @@
|
||||
/**
|
||||
* @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:file-edit', color: '#f59e0b' },
|
||||
{ id: 'review', label: 'Review', icon: 'lucide:eye', color: '#3b82f6' },
|
||||
{ id: 'pending', label: 'Pending Signatures', icon: 'lucide:pen-tool', color: '#8b5cf6' },
|
||||
{ id: 'signed', label: 'Signed', icon: 'lucide:check-circle', color: '#10b981' },
|
||||
{ id: 'executed', label: 'Executed', icon: 'lucide:shield-check', color: '#059669' },
|
||||
];
|
||||
|
||||
// Event type configuration
|
||||
const EVENT_TYPES = {
|
||||
created: { icon: 'lucide:plus-circle', color: '#10b981', label: 'Created' },
|
||||
updated: { icon: 'lucide:pencil', color: '#3b82f6', label: 'Updated' },
|
||||
status_change: { icon: 'lucide:arrow-right-circle', color: '#8b5cf6', label: 'Status Changed' },
|
||||
signature: { icon: 'lucide:pen-tool', color: '#10b981', label: 'Signature' },
|
||||
comment: { icon: 'lucide:message-circle', color: '#f59e0b', label: 'Comment' },
|
||||
attachment: { icon: 'lucide:paperclip', color: '#6366f1', label: 'Attachment' },
|
||||
viewed: { icon: 'lucide:eye', color: '#6b7280', label: 'Viewed' },
|
||||
shared: { icon: 'lucide:share-2', 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 .iconFA=${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 .iconFA=${'lucide:history'}></dees-icon>
|
||||
Activity Log
|
||||
</div>
|
||||
<button class="btn btn-secondary">
|
||||
<dees-icon .iconFA=${'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 .iconFA=${'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 .iconFA=${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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user