# Workflow Components Library

##### Workflow Status Badges

Draft Submitted 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}`);
            }
        }
    }