// Approval Panel Component class ApprovalPanel { constructor(options = {}) { this.container = null; this.changeset = null; this.currentUser = options.currentUser; this.onApprove = options.onApprove || (() => {}); this.onReject = options.onReject || (() => {}); this.onSubmit = options.onSubmit || (() => {}); } initialize(containerId) { this.container = document.getElementById(containerId); if (!this.container) { throw new Error(`Container element with id "${containerId}" not found`); } } setChangeset(changeset) { this.changeset = changeset; this.render(); } render() { if (!this.container || !this.changeset) return; const approvalState = this.changeset.approval_state || []; const currentStage = approvalState.find(s => s.status === 'pending') || approvalState[0]; this.container.innerHTML = `

Change Approval

${this._renderStatus()}
${this._renderStages(approvalState)}
${currentStage && this.changeset.status === 'pending_approval' ? this._renderApprovalActions(currentStage) : '' }
${this._renderHistory()}
`; this.attachEventListeners(); } _renderStatus() { const statusMap = { 'draft': { class: 'status-draft', text: 'Draft' }, 'pending_approval': { class: 'status-pending', text: 'Pending Approval' }, 'approved': { class: 'status-approved', text: 'Approved' }, 'rejected': { class: 'status-rejected', text: 'Rejected' }, 'applied': { class: 'status-applied', text: 'Applied' }, 'failed': { class: 'status-failed', text: 'Failed' }, 'reverted': { class: 'status-reverted', text: 'Reverted' } }; const status = statusMap[this.changeset.status] || statusMap.draft; return `
${status.text}
`; } _renderStages(stages) { return stages.map((stage, index) => `
${stage.name} ${ this._formatStageStatus(stage.status) }
${stage.required_roles.map(role => ` ${role} `).join('')}
${stage.approvers.length > 0 ? `
${stage.approvers.map(approver => this._renderApprover(approver)).join('')}
` : ''}
`).join(''); } _renderApprover(approver) { const decisionClass = approver.decision === 'approve' ? 'approved' : 'rejected'; return `
${approver.username} ${new Date(approver.timestamp).toLocaleString()}
${approver.decision === 'approve' ? 'Approved' : 'Rejected'}
${approver.comment ? `
${approver.comment}
` : ''}
`; } _renderApprovalActions(currentStage) { if (!this.canUserApprove(currentStage)) { return ''; } return `
`; } _renderHistory() { if (!this.changeset.approval_history?.length) { return ''; } return `
${this.changeset.approval_history.map(entry => `
${entry.username}
${this._formatHistoryAction(entry.action)}
${entry.comment ? `
${entry.comment}
` : ''}
`).join('')}
`; } _formatStageStatus(status) { const statusMap = { 'pending': 'Pending', 'approved': 'Approved', 'rejected': 'Rejected' }; return statusMap[status] || status; } _formatHistoryAction(action) { const actionMap = { 'submit': 'Submitted for approval', 'approve': 'Approved changes', 'reject': 'Rejected changes', 'revert': 'Reverted approval' }; return actionMap[action] || action; } canUserApprove(stage) { if (!this.currentUser) return false; // Check if user already approved const alreadyApproved = stage.approvers.some( a => a.user_id === this.currentUser.id ); if (alreadyApproved) return false; // Check if user has required role return stage.required_roles.some( role => this.currentUser.roles.includes(role) ); } async handleApprove() { const commentEl = this.container.querySelector('.approval-comment'); const comment = commentEl ? commentEl.value.trim() : ''; try { await this.onApprove(comment); this.render(); } catch (error) { console.error('Failed to approve:', error); // Show error message } } async handleReject() { const commentEl = this.container.querySelector('.approval-comment'); const comment = commentEl ? commentEl.value.trim() : ''; try { await this.onReject(comment); this.render(); } catch (error) { console.error('Failed to reject:', error); // Show error message } } attachEventListeners() { // Add any additional event listeners if needed } } export default ApprovalPanel;