Workflow Components Library

Workflow Status Badges
Draft Pending Approval Approved Rejected Completed Cancelled

JavaScript Function:
function getWorkflowStatusBadge(status) {
    const statusConfig = {
        'draft': { icon: 'file-earmark', label: 'Draft' },
        'submitted': { icon: 'send', label: 'Submitted' },
        'pending_approval': { icon: 'clock', label: 'Pending Approval' },
        'approved': { icon: 'check-circle', label: 'Approved' },
        'rejected': { icon: 'x-circle', label: 'Rejected' },
        'completed': { icon: 'check-all', label: 'Completed' },
        'cancelled': { icon: 'slash-circle', label: 'Cancelled' }
    };

    const config = statusConfig[status] || statusConfig['draft'];
    return `
         ${config.label}
    `;
}
Workflow Progress Tracker
Draft
Submitted
Manager Approval
3
Finance Approval
4
Approved
Workflow Timeline/History
Purchase Order Submitted

Submitted by John Doe

Jan 15, 2025 10:30 AM

Submit
Manager Approved

Approved by Jane Smith

"Budget approved for Q1"

Jan 15, 2025 2:45 PM

Approve
Pending Finance Approval

Assigned to Bob Johnson (Finance Director)

Waiting for 2 hours

Pending
Workflow Action Buttons
Full Workflow Card Example
Purchase Order #PO-2025-001
Submitted by John Doe
Jan 15, 2025
$12,500.00
Pending Finance Approval
Draft
Manager
Finance
4
Complete
Current Assignee: Bob Johnson (Finance Director)
Waiting for approval for 2 hours
JavaScript Workflow Widget Class
/**
 * WorkflowWidget - Reusable workflow component
 *
 * Usage:
 *   const widget = new WorkflowWidget('myContainer', {
 *       instanceId: 'workflow-123',
 *       apiBaseUrl: '/api/v1/workflows'
 *   });
 */
class WorkflowWidget {
    constructor(containerId, options = {}) {
        this.container = document.getElementById(containerId);
        this.options = {
            apiBaseUrl: options.apiBaseUrl || '/api/v1/workflows',
            instanceId: options.instanceId,
            showTimeline: options.showTimeline !== false,
            showProgress: options.showProgress !== false,
            showActions: options.showActions !== false,
            onAction: options.onAction || this.defaultActionHandler.bind(this)
        };

        this.instance = null;
        this.availableActions = [];

        if (this.options.instanceId) {
            this.load();
        }
    }

    async load() {
        try {
            // Load workflow instance
            const response = await fetch(
                `${this.options.apiBaseUrl}/instances/${this.options.instanceId}`
            );
            this.instance = await response.json();

            // Load available actions
            const actionsResponse = await fetch(
                `${this.options.apiBaseUrl}/instances/${this.options.instanceId}/available-actions`
            );
            this.availableActions = await actionsResponse.json();

            // Load history
            const historyResponse = await fetch(
                `${this.options.apiBaseUrl}/instances/${this.options.instanceId}/history`
            );
            this.history = await historyResponse.json();

            this.render();
        } catch (error) {
            console.error('Error loading workflow:', error);
            this.renderError(error);
        }
    }

    render() {
        if (!this.instance) return;

        let html = '
'; // Header html += this.renderHeader(); // Progress tracker if (this.options.showProgress) { html += this.renderProgress(); } // Timeline if (this.options.showTimeline) { html += this.renderTimeline(); } // Actions if (this.options.showActions && this.availableActions.length > 0) { html += this.renderActions(); } html += '
'; this.container.innerHTML = html; this.attachEventListeners(); } renderHeader() { return `
${this.instance.entity_type} #${this.instance.entity_id}
${this.getStatusBadge(this.instance.status)}
`; } renderActions() { let html = '
'; this.availableActions.forEach(action => { html += ` `; }); html += '
'; return html; } renderTimeline() { if (!this.history || this.history.length === 0) return ''; let html = '
'; this.history.forEach((action, index) => { const statusClass = this.getTimelineStatusClass(action.action); html += `
${action.to_state.name}

${action.action} by ${action.performed_by_user.username}

${action.comment ? `

"${action.comment}"

` : ''}

${new Date(action.action_date).toLocaleString()}

${action.action}
`; }); html += '
'; return html; } getStatusBadge(status) { return ` ${status.replace('_', ' ')} `; } getStatusIcon(status) { const icons = { 'draft': 'file-earmark', 'submitted': 'send', 'pending_approval': 'clock', 'approved': 'check-circle', 'rejected': 'x-circle', 'completed': 'check-all', 'cancelled': 'slash-circle' }; return icons[status] || 'circle'; } getActionIcon(action) { const icons = { 'submit': 'send', 'approve': 'check-circle', 'reject': 'x-circle', 'cancel': 'slash-circle', 'reassign': 'person-check', 'hold': 'pause-circle', 'resume': 'play-circle', 'revise': 'pencil-square' }; return icons[action] || 'circle'; } attachEventListeners() { // Attach click handlers to action buttons this.container.querySelectorAll('[data-action]').forEach(btn => { btn.addEventListener('click', (e) => { const action = e.currentTarget.dataset.action; const transitionId = e.currentTarget.dataset.transitionId; this.handleAction(action, transitionId); }); }); } async handleAction(action, transitionId) { const actionData = this.availableActions.find(a => a.transition_id === transitionId); // Show comment modal if required if (actionData && actionData.requires_comment) { this.showCommentModal(action, transitionId, actionData); } else { await this.executeAction(action, transitionId); } } async executeAction(action, transitionId, comment = null) { try { const response = await fetch( `${this.options.apiBaseUrl}/instances/${this.options.instanceId}/${action}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ comment }) } ); if (response.ok) { await this.load(); // Reload this.options.onAction(action, true); } else { throw new Error('Action failed'); } } catch (error) { console.error('Error executing action:', error); this.options.onAction(action, false, error); } } defaultActionHandler(action, success, error) { if (success) { alert(`Action "${action}" completed successfully!`); } else { alert(`Action "${action}" failed: ${error}`); } } }