mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 18:11:08 -05:00
234 lines
7.8 KiB
JavaScript
234 lines
7.8 KiB
JavaScript
// 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 = `
|
|
<div class="approval-panel">
|
|
<div class="approval-header">
|
|
<h3 class="approval-title">Change Approval</h3>
|
|
${this._renderStatus()}
|
|
</div>
|
|
|
|
<div class="approval-stages">
|
|
${this._renderStages(approvalState)}
|
|
</div>
|
|
|
|
${currentStage && this.changeset.status === 'pending_approval' ?
|
|
this._renderApprovalActions(currentStage) : ''
|
|
}
|
|
|
|
<div class="approval-history">
|
|
${this._renderHistory()}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 `
|
|
<div class="approval-status ${status.class}">
|
|
${status.text}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
_renderStages(stages) {
|
|
return stages.map((stage, index) => `
|
|
<div class="approval-stage ${stage.status}-stage">
|
|
<div class="stage-header">
|
|
<span class="stage-name">${stage.name}</span>
|
|
<span class="stage-status ${stage.status}">${
|
|
this._formatStageStatus(stage.status)
|
|
}</span>
|
|
</div>
|
|
<div class="stage-details">
|
|
<div class="required-roles">
|
|
${stage.required_roles.map(role => `
|
|
<span class="role-badge">${role}</span>
|
|
`).join('')}
|
|
</div>
|
|
${stage.approvers.length > 0 ? `
|
|
<div class="approvers-list">
|
|
${stage.approvers.map(approver => this._renderApprover(approver)).join('')}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
_renderApprover(approver) {
|
|
const decisionClass = approver.decision === 'approve' ? 'approved' : 'rejected';
|
|
return `
|
|
<div class="approver">
|
|
<div class="approver-info">
|
|
<span class="approver-name">${approver.username}</span>
|
|
<span class="approval-date">
|
|
${new Date(approver.timestamp).toLocaleString()}
|
|
</span>
|
|
</div>
|
|
<div class="approver-decision ${decisionClass}">
|
|
${approver.decision === 'approve' ? 'Approved' : 'Rejected'}
|
|
</div>
|
|
${approver.comment ? `
|
|
<div class="approver-comment">${approver.comment}</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
_renderApprovalActions(currentStage) {
|
|
if (!this.canUserApprove(currentStage)) {
|
|
return '';
|
|
}
|
|
|
|
return `
|
|
<div class="approval-actions">
|
|
<textarea
|
|
class="approval-comment"
|
|
placeholder="Add your comments (optional)"
|
|
></textarea>
|
|
<div class="action-buttons">
|
|
<button class="reject-button" onclick="this.handleReject()">
|
|
Reject Changes
|
|
</button>
|
|
<button class="approve-button" onclick="this.handleApprove()">
|
|
Approve Changes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
_renderHistory() {
|
|
if (!this.changeset.approval_history?.length) {
|
|
return '';
|
|
}
|
|
|
|
return `
|
|
<div class="approval-history-list">
|
|
${this.changeset.approval_history.map(entry => `
|
|
<div class="history-entry">
|
|
<div class="entry-header">
|
|
<span class="entry-user">${entry.username}</span>
|
|
<span class="entry-date">
|
|
${new Date(entry.timestamp).toLocaleString()}
|
|
</span>
|
|
</div>
|
|
<div class="entry-action ${entry.action}">
|
|
${this._formatHistoryAction(entry.action)}
|
|
</div>
|
|
${entry.comment ? `
|
|
<div class="entry-comment">${entry.comment}</div>
|
|
` : ''}
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
_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; |