Skip to content

Instantly share code, notes, and snippets.

@joshvasquez
Created November 16, 2025 00:50
Show Gist options
  • Select an option

  • Save joshvasquez/c198161f9d193b90f3d45e07945fe4f6 to your computer and use it in GitHub Desktop.

Select an option

Save joshvasquez/c198161f9d193b90f3d45e07945fe4f6 to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Approval Workflow Manager</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
margin-bottom: 24px;
color: #333;
}
.instructions {
background: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 12px 16px;
margin-bottom: 20px;
border-radius: 4px;
font-size: 14px;
color: #1565c0;
}
.workspace {
display: flex;
gap: 20px;
align-items: flex-start;
}
.unassigned-section {
flex: 0 0 280px;
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.workers-section {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.worker-column {
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: 200px;
}
.worker-column.drag-over {
background: #f0f7ff;
border: 2px dashed #3b82f6;
}
.worker-name {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.worker-count {
font-size: 12px;
color: #999;
margin-bottom: 12px;
}
.task-list {
display: flex;
flex-direction: column;
gap: 8px;
min-height: 60px;
}
.task-card {
background: #fafafa;
border: 1px solid #e5e5e5;
border-radius: 6px;
padding: 12px;
cursor: move;
transition:
box-shadow 0.2s,
background 0.2s;
position: relative;
}
.task-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.task-card:focus {
outline: 2px solid #2196f3;
outline-offset: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.task-card.dragging {
opacity: 0.5;
}
.task-card.selected {
background: #fff3e0;
border-color: #ff9800;
}
.task-workflow {
font-weight: 600;
color: #333;
margin-bottom: 4px;
font-size: 14px;
}
.task-person {
color: #666;
font-size: 13px;
margin-bottom: 4px;
}
.task-time {
color: #999;
font-size: 12px;
}
.empty-state {
color: #ccc;
font-size: 13px;
text-align: center;
padding: 20px;
}
.assignment-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
background: white;
border: 1px solid #ddd;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
min-width: 200px;
}
.assignment-menu.visible {
display: block;
}
.assignment-menu-title {
padding: 8px 12px;
font-size: 12px;
font-weight: 600;
color: #666;
border-bottom: 1px solid #eee;
}
.assignment-option {
padding: 10px 12px;
cursor: pointer;
font-size: 14px;
color: #333;
border: none;
background: none;
width: 100%;
text-align: left;
display: block;
}
.assignment-option:hover,
.assignment-option:focus {
background: #f5f5f5;
outline: none;
}
.assignment-option.current {
background: #e3f2fd;
color: #1976d2;
font-weight: 500;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.error {
background: #ffebee;
border-left: 4px solid #f44336;
padding: 12px 16px;
margin-bottom: 20px;
border-radius: 4px;
color: #c62828;
}
</style>
</head>
<body>
<div class="container">
<h1>Approval Workflow Manager</h1>
<div class="instructions">
<strong>Keyboard shortcuts:</strong> Select a task with Tab/Arrow keys,
press Enter or Space to assign, Escape to cancel
</div>
<div id="error-container"></div>
<div id="app"></div>
</div>
<script>
// ==================== API FUNCTIONS ====================
async function fetchTasks() {
// Simulating API call with mock data
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
workflow: "PTO Request",
person: "Sarah Johnson",
queueTime: "2h 15m",
assignedWorker: "",
},
{
id: 2,
workflow: "Expense Report",
person: "Mike Chen",
queueTime: "45m",
assignedWorker: "",
},
{
id: 3,
workflow: "Budget Approval",
person: "Emily Davis",
queueTime: "3h 30m",
assignedWorker: "",
},
{
id: 4,
workflow: "Vendor Contract",
person: "James Wilson",
queueTime: "1h 5m",
assignedWorker: "",
},
{
id: 5,
workflow: "Equipment Purchase",
person: "Lisa Martinez",
queueTime: "5h 20m",
assignedWorker: "Alice Cooper",
},
{
id: 6,
workflow: "PTO Request",
person: "David Brown",
queueTime: "30m",
assignedWorker: "Alice Cooper",
},
{
id: 7,
workflow: "Training Request",
person: "Anna Lee",
queueTime: "1h 45m",
assignedWorker: "Bob Smith",
},
]);
}, 500);
});
// Real API call would look like:
// const response = await fetch('/api/tasks');
// return await response.json();
}
async function updateTaskAssignment(taskId, workerName) {
// Simulating API call
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true });
}, 300);
});
// Real API call would look like:
// const response = await fetch(`/api/tasks/${taskId}`, {
// method: 'PATCH',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ assignedWorker: workerName })
// });
// return await response.json();
}
// ==================== STATE FUNCTIONS (PURE) ====================
function deriveWorkflowState(tasks, existingWorkers = new Set()) {
const workerMap = new Map();
const unassigned = [];
const allWorkers = new Set(existingWorkers);
tasks.forEach((task) => {
if (!task.assignedWorker || task.assignedWorker.trim() === "") {
unassigned.push(task);
} else {
allWorkers.add(task.assignedWorker);
if (!workerMap.has(task.assignedWorker)) {
workerMap.set(task.assignedWorker, []);
}
workerMap.get(task.assignedWorker).push(task);
}
});
// Initialize empty arrays for all known workers
allWorkers.forEach((worker) => {
if (!workerMap.has(worker)) {
workerMap.set(worker, []);
}
});
return { workerMap, unassigned, tasks, workers: allWorkers };
}
function getWorkers(state) {
return Array.from(state.workers).sort();
}
function getTasksForWorker(state, workerName) {
return state.workerMap.get(workerName) || [];
}
function assignTaskInState(state, taskId, newWorkerName) {
const updatedTasks = state.tasks.map((task) =>
task.id === taskId
? { ...task, assignedWorker: newWorkerName || "" }
: task,
);
// Preserve existing workers when reassigning
return deriveWorkflowState(updatedTasks, state.workers);
}
// ==================== APPLICATION STATE ====================
let appState = null;
let draggedTaskId = null;
let selectedTaskId = null;
let activeMenu = null;
// ==================== UI FUNCTIONS ====================
function showError(message) {
const errorContainer = document.getElementById("error-container");
errorContainer.innerHTML = `<div class="error">${message}</div>`;
setTimeout(() => {
errorContainer.innerHTML = "";
}, 5000);
}
function getWorkerNameForTask(task) {
if (!task.assignedWorker || task.assignedWorker.trim() === "") {
return "Unassigned";
}
return task.assignedWorker;
}
function createAssignmentMenu(task, card) {
const menu = document.createElement("div");
menu.className = "assignment-menu";
menu.role = "menu";
menu.setAttribute("aria-label", "Assign task to worker");
const title = document.createElement("div");
title.className = "assignment-menu-title";
title.textContent = "Assign to:";
menu.appendChild(title);
// Unassigned option
const unassignedBtn = document.createElement("button");
unassignedBtn.className = "assignment-option";
unassignedBtn.role = "menuitem";
unassignedBtn.textContent = "Unassigned";
if (!task.assignedWorker || task.assignedWorker.trim() === "") {
unassignedBtn.classList.add("current");
unassignedBtn.setAttribute("aria-current", "true");
}
unassignedBtn.addEventListener("click", () => {
handleAssignment(task.id, "");
closeMenu();
});
menu.appendChild(unassignedBtn);
// Worker options
const workers = getWorkers(appState);
workers.forEach((workerName) => {
const btn = document.createElement("button");
btn.className = "assignment-option";
btn.role = "menuitem";
btn.textContent = workerName;
if (task.assignedWorker === workerName) {
btn.classList.add("current");
btn.setAttribute("aria-current", "true");
}
btn.addEventListener("click", () => {
handleAssignment(task.id, workerName);
closeMenu();
});
menu.appendChild(btn);
});
return menu;
}
function openMenu(task, card) {
closeMenu();
const menu = createAssignmentMenu(task, card);
card.appendChild(menu);
menu.classList.add("visible");
activeMenu = menu;
selectedTaskId = task.id;
card.classList.add("selected");
const firstOption = menu.querySelector(".assignment-option");
if (firstOption) firstOption.focus();
const options = menu.querySelectorAll(".assignment-option");
options.forEach((option, index) => {
option.addEventListener("keydown", (e) => {
if (e.key === "ArrowDown") {
e.preventDefault();
const next = options[index + 1] || options[0];
next.focus();
} else if (e.key === "ArrowUp") {
e.preventDefault();
const prev = options[index - 1] || options[options.length - 1];
prev.focus();
} else if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
option.click();
} else if (e.key === "Escape") {
e.preventDefault();
closeMenu();
card.focus();
}
});
});
}
function closeMenu() {
if (activeMenu) {
activeMenu.remove();
activeMenu = null;
}
document.querySelectorAll(".task-card.selected").forEach((card) => {
card.classList.remove("selected");
});
selectedTaskId = null;
}
function createTaskCard(task) {
const card = document.createElement("div");
card.className = "task-card";
card.draggable = true;
card.tabIndex = 0;
card.role = "listitem";
card.dataset.taskId = task.id;
card.setAttribute(
"aria-label",
`${task.workflow} for ${task.person}, in queue ${task.queueTime}, assigned to ${getWorkerNameForTask(task)}`,
);
card.innerHTML = `
<div class="task-workflow">${task.workflow}</div>
<div class="task-person">${task.person}</div>
<div class="task-time">In queue: ${task.queueTime}</div>
<span class="sr-only">Currently assigned to: ${getWorkerNameForTask(task)}. Press Enter or Space to reassign.</span>
`;
card.addEventListener("dragstart", (e) => {
draggedTaskId = task.id;
card.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
});
card.addEventListener("dragend", () => {
card.classList.remove("dragging");
});
card.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
openMenu(task, card);
} else if (e.key === "Escape") {
closeMenu();
}
});
card.addEventListener("click", (e) => {
if (!e.target.closest(".assignment-menu")) {
if (activeMenu && activeMenu.parentElement === card) {
closeMenu();
} else {
openMenu(task, card);
}
}
});
return card;
}
function renderUnassigned() {
const container = document.createElement("div");
container.className = "unassigned-section";
container.innerHTML = `
<div class="section-title">Unassigned Tasks</div>
<div class="task-list" data-worker-name="" role="list" aria-label="Unassigned tasks"></div>
`;
const taskList = container.querySelector(".task-list");
if (appState.unassigned.length === 0) {
taskList.innerHTML =
'<div class="empty-state" role="status">No unassigned tasks</div>';
} else {
appState.unassigned.forEach((task) => {
taskList.appendChild(createTaskCard(task));
});
}
return container;
}
function renderWorkers() {
const container = document.createElement("div");
container.className = "workers-section";
const workers = getWorkers(appState);
workers.forEach((workerName) => {
const column = document.createElement("div");
column.className = "worker-column";
const workerTasks = getTasksForWorker(appState, workerName);
column.innerHTML = `
<div class="worker-name">${workerName}</div>
<div class="worker-count">${workerTasks.length} task${workerTasks.length !== 1 ? "s" : ""}</div>
<div class="task-list" data-worker-name="${workerName}" role="list" aria-label="Tasks for ${workerName}"></div>
`;
const taskList = column.querySelector(".task-list");
if (workerTasks.length === 0) {
taskList.innerHTML =
'<div class="empty-state" role="status">No tasks</div>';
} else {
workerTasks.forEach((task) => {
taskList.appendChild(createTaskCard(task));
});
}
container.appendChild(column);
});
return container;
}
function setupDropZones() {
const dropZones = document.querySelectorAll(".task-list");
dropZones.forEach((zone) => {
zone.addEventListener("dragover", (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
zone
.closest(".worker-column, .unassigned-section")
.classList.add("drag-over");
});
zone.addEventListener("dragleave", (e) => {
if (!zone.contains(e.relatedTarget)) {
zone
.closest(".worker-column, .unassigned-section")
.classList.remove("drag-over");
}
});
zone.addEventListener("drop", (e) => {
e.preventDefault();
zone
.closest(".worker-column, .unassigned-section")
.classList.remove("drag-over");
if (!draggedTaskId) return;
const workerName = zone.dataset.workerName;
handleAssignment(draggedTaskId, workerName);
});
});
}
function render() {
const appContainer = document.getElementById("app");
if (!appState) {
appContainer.innerHTML =
'<div class="loading">Loading tasks...</div>';
return;
}
appContainer.innerHTML = "";
const workspace = document.createElement("div");
workspace.className = "workspace";
workspace.appendChild(renderUnassigned());
workspace.appendChild(renderWorkers());
appContainer.appendChild(workspace);
setupDropZones();
}
// ==================== EVENT HANDLERS ====================
async function handleAssignment(taskId, newWorkerName) {
// Optimistic update
appState = assignTaskInState(appState, taskId, newWorkerName);
render();
try {
await updateTaskAssignment(taskId, newWorkerName);
} catch (error) {
showError("Failed to update assignment. Refreshing...");
// On error, refetch to get correct state
await initialize();
}
}
// ==================== INITIALIZATION ====================
async function initialize() {
try {
const tasks = await fetchTasks();
appState = deriveWorkflowState(tasks);
render();
} catch (error) {
showError("Failed to load tasks. Please refresh the page.");
console.error("Failed to load tasks:", error);
}
}
// Close menu when clicking outside
document.addEventListener("click", (e) => {
if (activeMenu && !e.target.closest(".task-card")) {
closeMenu();
}
});
// Start the app
initialize();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment