// 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 = `
${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 `
${approver.username}
${new Date(approver.timestamp).toLocaleString()}
${approver.decision === 'approve' ? 'Approved' : 'Rejected'}
${approver.comment ? `
` : ''}
`;
}
_renderApprovalActions(currentStage) {
if (!this.canUserApprove(currentStage)) {
return '';
}
return `
${this.changeset.approval_history.map(entry => `
${this._formatHistoryAction(entry.action)}
${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;