You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/docs/backlog/stories-kanban.html

1590 lines
78 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FloDoc Stories Kanban Board</title>
<style>
/* ========================================
FLODOC DESIGN SYSTEM IMPLEMENTATION
Primary Color: #784DC7
======================================== */
:root {
/* Brand Colors */
--color-primary: #784DC7;
--color-primary-light: #E9DFFC;
--color-primary-dark: #6A3FB8;
/* Neutral Colors */
--color-text-primary: #292929;
--color-text-secondary: #464646;
--color-text-muted: #9F9F9F;
--color-border: #E6E6E6;
--color-bg: #FFFFFF;
--color-bg-alt: #F8F8F8;
/* Status Colors */
--color-success: #83E281;
--color-success-text: #158212;
--color-error: #FF5964;
--color-warning: #FFE74C;
--color-info: #35A7FF;
/* Column Colors */
--color-backlog: #E6E6E6;
--color-todo: #FFE74C;
--color-progress: #35A7FF;
--color-review: #FF5964;
--color-done: #83E281;
/* Shadows */
--shadow-sm: 0px 2px 4px rgba(151, 71, 255, 0.08);
--shadow-md: 0px 4px 8px rgba(151, 71, 255, 0.08);
--shadow-lg: 0px 6px 12px rgba(41, 41, 41, 0.12);
--shadow-xl: 0px 10px 25px rgba(41, 41, 41, 0.15);
/* Borders & Radius */
--radius-sm: 4.75px;
--radius-md: 7.5px;
--radius-lg: 12px;
--border-width: 1px;
/* Spacing */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
/* Fonts */
--font-body: 'Segoe UI', system-ui, -apple-system, sans-serif;
--font-mono: 'Consolas', 'Monaco', monospace;
/* Layout */
--header-height: 64px;
--column-width: 320px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-body);
color: var(--color-text-primary);
background: var(--color-bg-alt);
overflow: hidden;
height: 100vh;
}
/* ========================================
HEADER
======================================== */
.app-header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background: var(--color-bg);
border-bottom: var(--border-width) solid var(--color-border);
display: flex;
align-items: center;
padding: 0 var(--space-lg);
z-index: 100;
box-shadow: var(--shadow-sm);
}
.logo-container {
display: flex;
align-items: center;
gap: var(--space-md);
}
.logo-icon {
width: 32px;
height: 32px;
background: var(--color-primary);
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 18px;
box-shadow: var(--shadow-sm);
}
.logo-text {
font-family: var(--font-body);
font-size: 20px;
font-weight: 700;
color: var(--color-text-primary);
letter-spacing: -0.5px;
}
.header-controls {
margin-left: auto;
display: flex;
align-items: center;
gap: var(--space-md);
}
.search-box {
padding: var(--space-sm) var(--space-md);
border: var(--border-width) solid var(--color-border);
border-radius: var(--radius-md);
font-size: 14px;
width: 250px;
transition: all 0.2s ease;
}
.search-box:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(120, 77, 199, 0.1);
}
.btn {
padding: var(--space-sm) var(--space-lg);
border: none;
border-radius: var(--radius-md);
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: var(--space-sm);
}
.btn-primary {
background: var(--color-primary);
color: white;
}
.btn-primary:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background: var(--color-bg);
color: var(--color-text-primary);
border: var(--border-width) solid var(--color-border);
}
.btn-secondary:hover {
background: var(--color-bg-alt);
border-color: var(--color-primary);
color: var(--color-primary);
}
.stats {
display: flex;
gap: var(--space-md);
font-size: 13px;
color: var(--color-text-muted);
}
.stat-item {
display: flex;
align-items: center;
gap: var(--space-xs);
}
.stat-value {
font-weight: 700;
color: var(--color-text-primary);
}
/* ========================================
KANBAN BOARD
======================================== */
.kanban-container {
position: fixed;
top: var(--header-height);
left: 0;
right: 0;
bottom: 0;
overflow-x: auto;
overflow-y: hidden;
padding: var(--space-lg);
background: var(--color-bg-alt);
}
.kanban-board {
display: flex;
gap: var(--space-lg);
height: 100%;
min-width: min-content;
}
.kanban-column {
flex-shrink: 0;
width: var(--column-width);
background: var(--color-bg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
display: flex;
flex-direction: column;
overflow: hidden;
border-top: 4px solid transparent;
}
.kanban-column.backlog {
border-top-color: var(--color-backlog);
}
.kanban-column.todo {
border-top-color: var(--color-todo);
}
.kanban-column.progress {
border-top-color: var(--color-progress);
}
.kanban-column.review {
border-top-color: var(--color-review);
}
.kanban-column.done {
border-top-color: var(--color-done);
}
.column-header {
padding: var(--space-lg);
border-bottom: var(--border-width) solid var(--color-border);
background: var(--color-bg-alt);
}
.column-title {
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
display: flex;
align-items: center;
justify-content: space-between;
}
.column-count {
background: var(--color-border);
color: var(--color-text-secondary);
padding: 2px 8px;
border-radius: var(--radius-lg);
font-size: 11px;
font-weight: 700;
}
.column-body {
flex: 1;
overflow-y: auto;
padding: var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.column-body.drag-over {
background: var(--color-primary-light);
}
/* ========================================
STORY CARD
======================================== */
.story-card {
background: var(--color-bg);
border: var(--border-width) solid var(--color-border);
border-radius: var(--radius-md);
padding: var(--space-md);
cursor: grab;
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
}
.story-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
border-color: var(--color-primary);
}
.story-card.dragging {
opacity: 0.5;
cursor: grabbing;
transform: rotate(2deg);
}
.story-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: var(--space-sm);
}
.story-number {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 700;
color: var(--color-primary);
background: var(--color-primary-light);
padding: 2px 6px;
border-radius: var(--radius-sm);
}
.story-title {
font-size: 13px;
font-weight: 700;
color: var(--color-text-primary);
line-height: 1.4;
margin-bottom: var(--space-sm);
}
.story-meta {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
margin-bottom: var(--space-sm);
}
.badge {
font-size: 10px;
font-weight: 700;
padding: 2px 6px;
border-radius: var(--radius-sm);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.badge-priority-high {
background: rgba(255, 89, 100, 0.15);
color: var(--color-error);
}
.badge-priority-critical {
background: rgba(255, 89, 100, 0.25);
color: var(--color-error);
font-weight: 800;
}
.badge-priority-medium {
background: rgba(255, 231, 76, 0.2);
color: #B8860B;
}
.badge-priority-low {
background: rgba(131, 226, 129, 0.2);
color: var(--color-success-text);
}
.badge-effort {
background: var(--color-bg-alt);
color: var(--color-text-secondary);
border: 1px solid var(--color-border);
}
.story-epic {
font-size: 11px;
color: var(--color-text-muted);
margin-bottom: var(--space-sm);
}
.story-preview {
font-size: 11px;
color: var(--color-text-secondary);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.story-actions {
display: flex;
gap: var(--space-xs);
margin-top: var(--space-sm);
opacity: 0;
transition: opacity 0.2s ease;
}
.story-card:hover .story-actions {
opacity: 1;
}
.story-action-btn {
padding: 4px 8px;
font-size: 11px;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
background: var(--color-bg-alt);
color: var(--color-text-secondary);
transition: all 0.2s ease;
}
.story-action-btn:hover {
background: var(--color-primary);
color: white;
}
/* ========================================
MODAL
======================================== */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(41, 41, 41, 0.7);
display: none;
align-items: center;
justify-content: center;
z-index: 200;
backdrop-filter: blur(4px);
}
.modal-overlay.active {
display: flex;
}
.modal {
background: var(--color-bg);
border-radius: var(--radius-lg);
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
box-shadow: var(--shadow-xl);
animation: modalSlideIn 0.3s ease;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.modal-header {
padding: var(--space-lg);
border-bottom: var(--border-width) solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
background: var(--color-bg-alt);
}
.modal-title {
font-size: 18px;
font-weight: 700;
color: var(--color-text-primary);
}
.modal-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: var(--color-text-muted);
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
transition: all 0.2s ease;
}
.modal-close:hover {
background: var(--color-bg-alt);
color: var(--color-text-primary);
}
.modal-body {
padding: var(--space-lg);
}
.modal-section {
margin-bottom: var(--space-xl);
}
.modal-section-title {
font-size: 14px;
font-weight: 700;
color: var(--color-primary);
margin-bottom: var(--space-md);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.modal-content {
background: var(--color-bg-alt);
padding: var(--space-lg);
border-radius: var(--radius-md);
line-height: 1.8;
color: var(--color-text-secondary);
font-size: 14px;
}
.modal-content strong {
color: var(--color-text-primary);
font-weight: 700;
}
.modal-content code {
background: var(--color-bg);
padding: 2px 6px;
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: 13px;
color: var(--color-primary);
border: 1px solid var(--color-border);
}
.modal-content ul,
.modal-content ol {
margin-left: var(--space-lg);
margin-top: var(--space-sm);
}
.modal-content li {
margin-bottom: var(--space-sm);
}
/* ========================================
SCROLLBAR
======================================== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--color-bg-alt);
}
::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: var(--radius-sm);
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-text-muted);
}
/* ========================================
RESPONSIVE
======================================== */
@media (max-width: 768px) {
:root {
--column-width: 280px;
}
.header-controls {
gap: var(--space-sm);
}
.search-box {
width: 150px;
}
.stats {
display: none;
}
.btn span {
display: none;
}
.modal {
width: 95%;
}
}
/* ========================================
TOAST NOTIFICATION
======================================== */
.toast {
position: fixed;
bottom: var(--space-lg);
right: var(--space-lg);
background: var(--color-primary);
color: white;
padding: var(--space-md) var(--space-lg);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
display: none;
align-items: center;
gap: var(--space-md);
z-index: 300;
animation: toastSlideIn 0.3s ease;
}
.toast.active {
display: flex;
}
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.toast.success {
background: var(--color-success);
color: var(--color-success-text);
}
.toast.error {
background: var(--color-error);
color: white;
}
/* ========================================
EMPTY STATE
======================================== */
.empty-column {
text-align: center;
padding: var(--space-xl);
color: var(--color-text-muted);
font-size: 13px;
font-style: italic;
}
/* ========================================
LOADING
======================================== */
.loading {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--color-primary);
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--color-border);
border-top-color: var(--color-primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<!-- Header -->
<header class="app-header">
<div class="logo-container">
<div class="logo-icon">F</div>
<div class="logo-text">FloDoc Kanban</div>
</div>
<div class="header-controls">
<input
type="text"
class="search-box"
id="searchBox"
placeholder="Search stories..."
autocomplete="off"
>
<div class="stats">
<div class="stat-item">
<span>Total:</span>
<span class="stat-value" id="statTotal">0</span>
</div>
<div class="stat-item">
<span>Done:</span>
<span class="stat-value" id="statDone">0</span>
</div>
</div>
<button class="btn btn-secondary" id="resetBtn">
<span></span>
<span>Reset</span>
</button>
<button class="btn btn-primary" id="saveBtn">
<span>💾</span>
<span>Save</span>
</button>
</div>
</header>
<!-- Kanban Board -->
<div class="kanban-container">
<div class="kanban-board" id="kanbanBoard">
<!-- Columns will be dynamically generated -->
</div>
</div>
<!-- Modal for Story Details -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modalTitle">Story Details</div>
<button class="modal-close" id="modalClose">×</button>
</div>
<div class="modal-body" id="modalBody">
<!-- Content will be dynamically generated -->
</div>
</div>
</div>
<!-- Toast Notification -->
<div class="toast" id="toast">
<span id="toastMessage">Action completed</span>
</div>
<script>
// ========================================
// FLODOC STORIES DATA
// ========================================
const storiesData = [
{
number: "1.1",
title: "Database Schema Extension",
priority: "Critical",
epic: "Phase 1 - Foundation",
effort: "2-3 days",
risk: "Low",
user_story: "**As a** system architect,\n**I want** to create the database schema for FloDoc's new models,\n**So that** the application has the foundation to support cohort management.",
background: "Based on the PRD analysis, we need three new tables to support the 3-portal cohort management system:\n- `institutions` - Single training institution (not multi-tenant)\n- `cohorts` - Training program cohorts\n- `cohort_enrollments` - Student enrollments in cohorts\n\nThese tables must integrate with existing DocuSeal tables without breaking existing functionality.",
acceptance: "**Functional:**\n- ✅ All three tables created with correct schema\n- ✅ Foreign key relationships established\n- ✅ All indexes created for performance\n- ✅ Migrations are reversible\n- ✅ No modifications to existing DocuSeal tables\n\n**Integration:**\n- ✅ IV1: Existing DocuSeal tables remain unchanged\n- ✅ IV2: New tables can reference existing tables (templates, submissions)\n- ✅ IV3: Database performance not degraded\n\n**Security:**\n- ✅ All tables include `deleted_at` for soft deletes\n- ✅ Sensitive fields encrypted if required\n- ✅ Foreign keys prevent orphaned records\n\n**Quality:**\n- ✅ Migrations follow Rails conventions\n- ✅ Consistent naming with existing codebase\n- ✅ All migrations include `down` method"
},
{
number: "1.2",
title: "Core Models Implementation",
priority: "High",
epic: "Phase 1 - Foundation",
effort: "2 days",
risk: "Low",
user_story: "**As a** developer,\n**I want** to create ActiveRecord models for the new FloDoc tables,\n**So that** the application can interact with cohorts and enrollments programmatically.",
background: "Models must follow existing DocuSeal patterns:\n- Inherit from `ApplicationRecord`\n- Use `strip_attributes` for data cleaning\n- Include soft delete functionality\n- Define proper associations and validations\n- Follow naming conventions",
acceptance: "**Functional:**\n- ✅ All three models created with correct class structure\n- ✅ All associations defined correctly\n- ✅ All validations implemented\n- ✅ All scopes defined\n- ✅ Model methods work as specified\n\n**Integration:**\n- ✅ IV1: Models don't break existing DocuSeal models\n- ✅ IV2: Associations work with existing tables\n- ✅ IV3: Query performance acceptable with 1000+ records\n\n**Security:**\n- ✅ No mass assignment vulnerabilities\n- ✅ Proper attribute whitelisting\n- ✅ Email validation on all email fields\n\n**Quality:**\n- ✅ Follow existing code style (RuboCop)\n- ✅ All methods have YARD comments\n- ✅ Test coverage > 80%"
},
{
number: "1.3",
title: "Authorization Layer Extension",
priority: "High",
epic: "Phase 1 - Foundation",
effort: "1-2 days",
risk: "Medium",
user_story: "**As a** system administrator,\n**I want** the authorization system to support FloDoc roles and permissions,\n**So that** users can only access appropriate cohort management functions.",
background: "Extend Cancancan abilities to support:\n- TP Admin: Full cohort management\n- Student: View own enrollments, upload documents\n- Sponsor: Review and sign documents\n- Admin: All access plus system configuration\n\nMust work with existing DocuSeal permissions without conflicts.",
acceptance: "**Functional:**\n- ✅ Ability rules defined for all FloDoc actions\n- ✅ Role-based access control working\n- ✅ Existing DocuSeal permissions unaffected\n- ✅ Authorization checks on all endpoints\n\n**Integration:**\n- ✅ IV1: Works with existing authentication\n- ✅ IV2: No conflicts with DocuSeal abilities\n- ✅ IV3: Performance impact minimal\n\n**Security:**\n- ✅ No privilege escalation possible\n- ✅ All routes protected\n- ✅ API endpoints properly secured\n\n**Quality:**\n- ✅ Ability tests for all roles\n- ✅ Clear error messages for unauthorized access"
},
{
number: "2.1",
title: "Cohort Creation & Management",
priority: "High",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** to create and manage cohorts with all their configuration details,\n**So that** I can organize students into training programs and prepare them for the signature workflow.",
background: "Cohort creation requires:\n- Name and program type selection\n- Sponsor email configuration\n- Required student uploads (ID, matric, tertiary)\n- Cohort metadata for additional info\n- Status tracking (draft/active/completed)\n\nMust link to existing DocuSeal templates.",
acceptance: "**Functional:**\n- ✅ Cohort creation form with all fields\n- ✅ Cohort update functionality\n- ✅ Status management (draft → active → completed)\n- ✅ Template linking works correctly\n\n**UI/UX:**\n- ✅ Intuitive form validation\n- ✅ Clear success/error feedback\n- ✅ Progress indicators\n\n**Integration:**\n- ✅ IV1: Creates records in cohorts table\n- ✅ IV2: Links to templates table\n- ✅ IV3: No performance issues\n\n**Security:**\n- ✅ Only TP admins can create cohorts\n- ✅ Input validation prevents injection\n- ✅ Sponsor email format validation"
},
{
number: "2.2",
title: "TP Signing Phase Logic",
priority: "Critical",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "High",
user_story: "**As a** TP administrator,\n**I want** to sign the first student's document and have that signing replicated to all other students in the cohort,\n**So that** I don't need to sign each student's document individually, saving time and eliminating duplicate sponsor emails.",
background: "This is the CORE INNOVATION of FloDoc:\n- TP signs ONE document\n- System replicates signature to all students\n- Must work with DocuSeal's existing signing mechanism\n- Prototype required before full implementation\n- High risk due to integration complexity",
acceptance: "**Functional:**\n- ✅ TP signs first document\n- ✅ Signature replicated to all cohort students\n- ✅ All documents show identical TP signature\n- ✅ No duplicate sponsor emails sent\n\n**Integration:**\n- ✅ IV1: Works with existing DocuSeal signing\n- ✅ IV2: Replication doesn't break submissions\n- ✅ IV3: Performance acceptable for 50+ students\n\n**Security:**\n- ✅ Only TP can trigger replication\n- ✅ Signatures are cryptographically valid\n- ✅ Audit trail shows replication event\n\n**Quality:**\n- ✅ Prototype tested with real PDFs\n- ✅ Rollback plan documented"
},
{
number: "2.3",
title: "Student Enrollment Management",
priority: "High",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** to manage student enrollment in cohorts and bulk-create student submissions,\n**So that** students can access their documents to complete after TP signs.",
background: "Student enrollment workflow:\n- Bulk import via CSV or manual entry\n- Create cohort_enrollment records\n- Generate submissions linked to students\n- Track enrollment status (waiting/in_progress/complete)\n- Store uploaded documents",
acceptance: "**Functional:**\n- ✅ Bulk student import (CSV)\n- ✅ Individual student enrollment\n- ✅ Submission creation for each student\n- ✅ Status tracking per enrollment\n- ✅ Document upload tracking\n\n**UI/UX:**\n- ✅ Bulk import with validation\n- ✅ Clear error reporting\n- ✅ Progress indicators for bulk operations\n\n**Integration:**\n- ✅ IV1: Creates submissions in existing table\n- ✅ IV2: Links to cohort_enrollments correctly\n- ✅ IV3: Handles 100+ students efficiently\n\n**Security:**\n- ✅ Email validation for students\n- ✅ Prevent duplicate enrollments\n- ✅ File type validation for uploads"
},
{
number: "2.4",
title: "Sponsor Review Workflow",
priority: "Medium",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "Low",
user_story: "**As a** sponsor,\n**I want** to receive and review student documents,\n**So that** I can verify information before signing.",
background: "Sponsor workflow:\n- Receive email with cohort overview\n- Access documents via secure token\n- Review student information\n- Verify uploaded documents\n- Prepare for bulk signing",
acceptance: "**Functional:**\n- ✅ Email notification to sponsor\n- ✅ Token-based access to documents\n- ✅ Document preview functionality\n- ✅ Student information display\n\n**Integration:**\n- ✅ IV1: Works with existing email system\n- ✅ IV2: Token system secure\n- ✅ IV3: Document rendering works\n\n**Security:**\n- ✅ Tokens expire after use\n- ✅ Secure token generation\n- ✅ No unauthorized access possible\n\n**Quality:**\n- ✅ Email templates designed\n- ✅ Token refresh mechanism"
},
{
number: "2.5",
title: "TP Review & Finalization",
priority: "Medium",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** to review all student submissions and finalize the cohort,\n**So that** I can ensure everything is complete before closing.",
background: "Finalization workflow:\n- View all student statuses\n- Check completion rates\n- Review any issues\n- Mark cohort as finalized\n- Generate final reports",
acceptance: "**Functional:**\n- ✅ Cohort overview dashboard\n- ✅ Completion status tracking\n- ✅ Finalization action\n- ✅ Status updates propagate\n\n**Integration:**\n- ✅ IV1: Updates cohort status\n- ✅ IV2: Triggers completion events\n- ✅ IV3: No data integrity issues\n\n**Security:**\n- ✅ Only TP can finalize\n- ✅ Prevents premature finalization\n- ✅ Audit trail maintained\n\n**Quality:**\n- ✅ Validation before finalization\n- ✅ Clear error messages"
},
{
number: "2.6",
title: "Excel Export for Cohort Data",
priority: "Medium",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** to export cohort data to Excel,\n**So that** I can analyze enrollment and completion data externally.",
background: "Export requirements (FR23):\n- Export all cohort data\n- Include student information\n- Show completion status\n- Include timestamps\n- Professional formatting",
acceptance: "**Functional:**\n- ✅ Excel export button\n- ✅ All relevant data included\n- ✅ Proper formatting and headers\n- ✅ Download functionality works\n\n**Integration:**\n- ✅ IV1: Queries all necessary tables\n- ✅ IV2: Handles large datasets\n- ✅ IV3: No performance impact\n\n**Security:**\n- ✅ Only authorized users can export\n- ✅ Sensitive data handled properly\n\n**Quality:**\n- ✅ File naming convention\n- ✅ Error handling for large exports"
},
{
number: "2.7",
title: "Audit Log & Compliance",
priority: "Medium",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "Low",
user_story: "**As a** compliance officer,\n**I want** all actions logged in an audit trail,\n**So that** I can track who did what and when.",
background: "Audit requirements:\n- Log all cohort actions\n- Track user who performed action\n- Timestamp all events\n- Store before/after values\n- Queryable audit trail",
acceptance: "**Functional:**\n- ✅ Audit log table created\n- ✅ All actions logged\n- ✅ User attribution working\n- ✅ Timestamps accurate\n\n**Integration:**\n- ✅ IV1: Logs all FloDoc actions\n- ✅ IV2: Doesn't slow down operations\n- ✅ IV3: Queryable efficiently\n\n**Security:**\n- ✅ Audit logs tamper-proof\n- ✅ Only admins can view\n- ✅ Retention policy defined\n\n**Quality:**\n- ✅ Search/filter functionality\n- ✅ Export audit logs"
},
{
number: "2.8",
title: "Cohort State Machine",
priority: "High",
epic: "Phase 2 - Backend Logic",
effort: "4 hours",
risk: "Medium",
user_story: "**As a** system,\n**I want** to manage cohort state transitions automatically,\n**So that** the workflow progresses smoothly without manual intervention.",
background: "State machine for cohorts:\n- draft → active (when TP signs)\n- active → students_complete (when all students finish)\n- students_complete → sponsor_complete (when sponsor signs)\n- sponsor_complete → finalized (when TP reviews)\n\nMust handle edge cases and rollbacks.",
acceptance: "**Functional:**\n- ✅ State transitions defined\n- ✅ Automatic progression works\n- ✅ Edge cases handled\n- ✅ Rollback capability\n\n**Integration:**\n- ✅ IV1: Triggers on events\n- ✅ IV2: Updates all related records\n- ✅ IV3: No race conditions\n\n**Security:**\n- ✅ Invalid transitions prevented\n- ✅ State validation\n\n**Quality:**\n- ✅ State machine tests\n- ✅ Clear state documentation"
},
{
number: "3.1",
title: "RESTful Cohort Management API",
priority: "High",
epic: "Phase 3 - API Layer",
effort: "4 hours",
risk: "Low",
user_story: "**As a** developer,\n**I want** RESTful APIs for cohort management,\n**So that** I can integrate FloDoc with other systems.",
background: "API endpoints needed:\n- GET /api/cohorts - List cohorts\n- POST /api/cohorts - Create cohort\n- GET /api/cohorts/:id - Show cohort\n- PUT /api/cohorts/:id - Update cohort\n- DELETE /api/cohorts/:id - Delete cohort\n- POST /api/cohorts/:id/enroll - Enroll students",
acceptance: "**Functional:**\n- ✅ All CRUD endpoints implemented\n- ✅ Proper HTTP status codes\n- ✅ JSON responses\n- ✅ Pagination support\n\n**Integration:**\n- ✅ IV1: Uses existing models\n- ✅ IV2: Authentication required\n- ✅ IV3: Rate limiting in place\n\n**Security:**\n- ✅ JWT authentication\n- ✅ Input validation\n- ✅ No SQL injection\n\n**Quality:**\n- ✅ API documentation\n- ✅ Error handling"
},
{
number: "3.2",
title: "Webhook Events",
priority: "Medium",
epic: "Phase 3 - API Layer",
effort: "4 hours",
risk: "Low",
user_story: "**As a** system integrator,\n**I want** webhook notifications for state changes,\n**So that** external systems can react to FloDoc events.",
background: "Webhook events:\n- cohort.created\n- cohort.state_changed\n- student.enrolled\n- student.completed\n- sponsor.signed\n- cohort.finalized\n\nMust support retries and exponential backoff.",
acceptance: "**Functional:**\n- ✅ Webhook event system\n- ✅ All required events implemented\n- ✅ Configurable endpoints\n- ✅ Retry logic with backoff\n\n**Integration:**\n- ✅ IV1: Events trigger correctly\n- ✅ IV2: Payloads include all data\n- ✅ IV3: No blocking on delivery\n\n**Security:**\n- ✅ HMAC signature verification\n- ✅ HTTPS only\n- ✅ Endpoint validation\n\n**Quality:**\n- ✅ Webhook delivery logs\n- ✅ Failed delivery alerts"
},
{
number: "3.3",
title: "Student API (Ad-hoc)",
priority: "High",
epic: "Phase 3 - API Layer",
effort: "4 hours",
risk: "Medium",
user_story: "**As a** student,\n**I want** to access my documents via token-based API,\n**So that** I can complete forms without creating an account.",
background: "Ad-hoc access pattern:\n- Student receives email with token\n- Token grants access to specific submission\n- No user account required\n- Token expires after completion\n- Limited scope (own documents only)",
acceptance: "**Functional:**\n- ✅ Token generation endpoint\n- ✅ Token validation\n- ✅ Document access via token\n- ✅ Form submission via token\n- ✅ Token expiration\n\n**Integration:**\n- ✅ IV1: Works with submissions table\n- ✅ IV2: Secure token storage\n- ✅ IV3: No account creation needed\n\n**Security:**\n- ✅ Short-lived tokens\n- ✅ One-time use tokens\n- ✅ Scope-limited access\n\n**Quality:**\n- ✅ Token refresh mechanism\n- ✅ Clear error messages"
},
{
number: "3.4",
title: "API Documentation",
priority: "Low",
epic: "Phase 3 - API Layer",
effort: "4 hours",
risk: "Low",
user_story: "**As a** developer,\n**I want** comprehensive API documentation,\n**So that** I can easily integrate with FloDoc.",
background: "Documentation requirements:\n- OpenAPI/Swagger specs\n- Endpoint descriptions\n- Request/response examples\n- Authentication details\n- Error code reference",
acceptance: "**Functional:**\n- ✅ OpenAPI specification\n- ✅ Interactive API docs\n- ✅ Code examples\n- ✅ Error documentation\n\n**Integration:**\n- ✅ IV1: Matches actual API\n- ✅ IV2: Auto-generated from code\n- ✅ IV3: Versioned API\n\n**Quality:**\n- ✅ Clear examples\n- ✅ Searchable docs"
},
{
number: "4.1",
title: "Cohort Management Dashboard",
priority: "High",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** a dashboard showing all cohorts,\n**So that** I can monitor all training programs at a glance.",
background: "Dashboard features:\n- List of all cohorts\n- Status indicators\n- Quick stats (total, active, completed)\n- Search and filter\n- Navigation to detail views",
acceptance: "**Functional:**\n- ✅ Cohort list view\n- ✅ Status badges\n- ✅ Summary statistics\n- ✅ Search/filter functionality\n\n**UI/UX:**\n- ✅ Clean, professional design\n- ✅ Responsive layout\n- ✅ Clear visual hierarchy\n- ✅ Fast loading\n\n**Integration:**\n- ✅ IV1: Fetches from cohorts table\n- ✅ IV2: Real-time updates\n- ✅ IV3: No performance issues\n\n**Security:**\n- ✅ Only accessible to TP admins"
},
{
number: "4.2",
title: "Cohort Creation & Bulk Import",
priority: "High",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** to create cohorts and bulk import students,\n**So that** I can quickly set up new training programs.",
background: "Creation interface:\n- Cohort form (name, program type, sponsor email)\n- Required uploads configuration\n- Bulk student import (CSV)\n- Validation and preview\n- Confirmation workflow",
acceptance: "**Functional:**\n- ✅ Cohort creation form\n- ✅ CSV upload with validation\n- ✅ Preview before confirm\n- ✅ Bulk enrollment creation\n\n**UI/UX:**\n- ✅ Drag-and-drop CSV upload\n- ✅ Clear validation errors\n- ✅ Progress indicators\n- ✅ Success confirmation\n\n**Integration:**\n- ✅ IV1: Creates cohort records\n- ✅ IV2: Creates enrollments\n- ✅ IV3: Handles large files\n\n**Security:**\n- ✅ File type validation\n- ✅ Input sanitization"
},
{
number: "4.3",
title: "Cohort Detail Overview",
priority: "Medium",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** to view detailed cohort information,\n**So that** I can monitor progress and manage individual students.",
background: "Detail view features:\n- Cohort metadata\n- Student list with statuses\n- Progress indicators\n- Action buttons (enroll, finalize, export)\n- Timeline of events",
acceptance: "**Functional:**\n- ✅ Cohort information display\n- ✅ Student list with filtering\n- ✅ Status tracking\n- ✅ Action buttons\n\n**UI/UX:**\n- ✅ Tabbed interface\n- ✅ Sortable tables\n- ✅ Status color coding\n- ✅ Responsive design\n\n**Integration:**\n- ✅ IV1: Fetches all related data\n- ✅ IV2: Real-time status updates\n- ✅ IV3: Efficient queries\n\n**Security:**\n- ✅ Access control checks"
},
{
number: "4.4",
title: "TP Signing Interface",
priority: "High",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Medium",
user_story: "**As a** TP administrator,\n**I want** a signing interface for the first student,\n**So that** I can sign once and replicate to all students.",
background: "Signing interface:\n- Preview first student's document\n- Digital signature capture\n- Replication confirmation\n- Progress indicator\n- Success notification",
acceptance: "**Functional:**\n- ✅ Document preview\n- ✅ Signature capture\n- ✅ Replication trigger\n- ✅ Status update\n\n**UI/UX:**\n- ✅ Clear signing workflow\n- ✅ Preview before sign\n- ✅ Confirmation dialog\n- ✅ Success feedback\n\n**Integration:**\n- ✅ IV1: Integrates with DocuSeal\n- ✅ IV2: Triggers replication\n- ✅ IV3: Updates all students\n\n**Security:**\n- ✅ Explicit confirmation required\n- ✅ Audit log entry"
},
{
number: "4.5",
title: "Student Management View",
priority: "Medium",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** to manage individual student enrollments,\n**So that** I can add/remove students and track their progress.",
background: "Student management:\n- Add individual students\n- Remove students\n- View student details\n- Track document uploads\n- Resend invitations",
acceptance: "**Functional:**\n- ✅ Add/remove students\n- ✅ Student detail view\n- ✅ Document upload tracking\n- ✅ Invitation resend\n\n**UI/UX:**\n- ✅ Inline editing\n- ✅ Quick actions\n- ✅ Status indicators\n- ✅ Confirmation dialogs\n\n**Integration:**\n- ✅ IV1: Updates enrollments\n- ✅ IV2: Triggers notifications\n- ✅ IV3: Real-time updates\n\n**Security:**\n- ✅ Validation on all inputs"
},
{
number: "4.6",
title: "Sponsor Portal Dashboard",
priority: "High",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** sponsor,\n**I want** a dashboard showing cohorts awaiting my signature,\n**So that** I can quickly see what needs my review.",
background: "Sponsor dashboard:\n- List of cohorts needing signature\n- Student count per cohort\n- Progress indicators\n- Quick access to review\n- Token-based authentication",
acceptance: "**Functional:**\n- ✅ Cohort list for sponsor\n- ✅ Student counts\n- ✅ Progress visualization\n- ✅ Review button\n\n**UI/UX:**\n- ✅ Clean, focused design\n- ✅ Mobile responsive\n- ✅ Clear call-to-action\n- ✅ Minimal clicks to sign\n\n**Integration:**\n- ✅ IV1: Token-based access\n- ✅ IV2: Filters by sponsor email\n- ✅ IV3: Secure document access\n\n**Security:**\n- ✅ Token validation\n- ✅ Email verification"
},
{
number: "4.7",
title: "Sponsor Bulk Signing",
priority: "High",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Medium",
user_story: "**As a** sponsor,\n**I want** to sign multiple student documents at once,\n**So that** I can complete my review efficiently.",
background: "Bulk signing:\n- Select multiple students\n- Single signature application\n- Confirmation before signing\n- Progress indicator\n- Completion notification",
acceptance: "**Functional:**\n- ✅ Multi-select students\n- ✅ Single signature application\n- ✅ Confirmation dialog\n- ✅ Batch processing\n\n**UI/UX:**\n- ✅ Checkbox selection\n- ✅ Select all option\n- ✅ Clear signing preview\n- ✅ Success feedback\n\n**Integration:**\n- ✅ IV1: Updates all submissions\n- ✅ IV2: Triggers notifications\n- ✅ IV3: Audit trail\n\n**Security:**\n- ✅ Explicit confirmation\n- ✅ Cannot sign twice"
},
{
number: "4.8",
title: "Sponsor Progress Tracking",
priority: "Medium",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** sponsor,\n**I want** to track progress of all students in a cohort,\n**So that** I can see who has completed and who hasn't.",
background: "Progress tracking:\n- Visual progress bars\n- Student status list\n- Completion percentages\n- Filter by status\n- Timeline view",
acceptance: "**Functional:**\n- ✅ Progress visualization\n- ✅ Student status list\n- ✅ Completion metrics\n- ✅ Status filtering\n\n**UI/UX:**\n- ✅ Color-coded status\n- ✅ Progress bars\n- ✅ Sortable columns\n- ✅ Responsive layout\n\n**Integration:**\n- ✅ IV1: Real-time status\n- ✅ IV2: Efficient queries\n- ✅ IV3: No performance issues\n\n**Security:**\n- ✅ Only shows sponsor's cohorts"
},
{
number: "4.9",
title: "Sponsor Token Renewal",
priority: "Medium",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** sponsor,\n**I want** to renew my access token and manage my session,\n**So that** I can continue working without interruption.",
background: "Session management:\n- Token refresh mechanism\n- Session timeout warnings\n- Logout functionality\n- Token renewal UI\n- Persistent login option",
acceptance: "**Functional:**\n- ✅ Token refresh\n- ✅ Session timeout warnings\n- ✅ Logout functionality\n- ✅ Renewal UI\n\n**UI/UX:**\n- ✅ Clear session status\n- ✅ Warnings before timeout\n- ✅ Easy renewal\n- ✅ No unexpected logouts\n\n**Integration:**\n- ✅ IV1: Secure token handling\n- ✅ IV2: Automatic refresh\n- ✅ IV3: No data loss\n\n**Security:**\n- ✅ Short session lifetimes\n- ✅ Secure token storage"
},
{
number: "4.10",
title: "TP Portal Analytics",
priority: "Medium",
epic: "Phase 4 - Admin Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** TP administrator,\n**I want** analytics and monitoring for all cohorts,\n**So that** I can identify bottlenecks and optimize workflows.",
background: "Analytics features:\n- Cohort completion rates\n- Average completion time\n- Bottleneck identification\n- Export analytics\n- Visual dashboards",
acceptance: "**Functional:**\n- ✅ Analytics dashboard\n- ✅ Completion metrics\n- ✅ Time-based analysis\n- ✅ Export functionality\n\n**UI/UX:**\n- ✅ Visual charts\n- ✅ Interactive filters\n- ✅ Clear insights\n- ✅ Professional design\n\n**Integration:**\n- ✅ IV1: Aggregates data\n- ✅ IV2: Efficient queries\n- ✅ IV3: Real-time updates\n\n**Security:**\n- ✅ Only accessible to admins"
},
{
number: "5.1",
title: "Student Document Upload",
priority: "High",
epic: "Phase 5 - Student Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** student,\n**I want** to upload required documents,\n**So that** I can complete my enrollment requirements.",
background: "Document upload:\n- List of required documents\n- Drag-and-drop upload\n- File type validation\n- Upload progress\n- Success/error feedback",
acceptance: "**Functional:**\n- ✅ Required document list\n- ✅ File upload interface\n- ✅ Type validation\n- ✅ Progress indicator\n\n**UI/UX:**\n- ✅ Drag-and-drop support\n- ✅ Clear instructions\n- ✅ Visual feedback\n- ✅ Mobile friendly\n\n**Integration:**\n- ✅ IV1: Stores in Active Storage\n- ✅ IV2: Updates enrollment record\n- ✅ IV3: Validates requirements\n\n**Security:**\n- ✅ File type restrictions\n- ✅ Size limits\n- ✅ Virus scanning"
},
{
number: "5.2",
title: "Student Form Filling",
priority: "High",
epic: "Phase 5 - Student Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** student,\n**I want** to fill out form fields,\n**So that** I can complete my document submission.",
background: "Form filling:\n- All required fields displayed\n- Field validation\n- Save progress\n- Field completion tracking\n- Signature capture",
acceptance: "**Functional:**\n- ✅ All form fields present\n- ✅ Field validation\n- ✅ Save functionality\n- ✅ Signature capture\n\n**UI/UX:**\n- ✅ Clear field labels\n- ✅ Inline validation\n- ✅ Progress indicators\n- ✅ Intuitive flow\n\n**Integration:**\n- ✅ IV1: Updates submitter values\n- ✅ IV2: Validates all fields\n- ✅ IV3: Saves to database\n\n**Security:**\n- ✅ Input sanitization\n- ✅ Required field validation"
},
{
number: "5.3",
title: "Student Progress & Save Draft",
priority: "Medium",
epic: "Phase 5 - Student Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** student,\n**I want** to save my progress and resume later,\n**So that** I can complete the form at my own pace.",
background: "Progress saving:\n- Auto-save functionality\n- Manual save button\n- Draft status\n- Resume from last position\n- Completion percentage",
acceptance: "**Functional:**\n- ✅ Auto-save every 30 seconds\n- ✅ Manual save button\n- ✅ Draft state persistence\n- ✅ Resume functionality\n\n**UI/UX:**\n- ✅ Save indicator\n- ✅ Last saved timestamp\n- ✅ Progress percentage\n- ✅ Clear save status\n\n**Integration:**\n- ✅ IV1: Updates values field\n- ✅ IV2: Preserves draft state\n- ✅ IV3: No data loss\n\n**Security:**\n- ✅ Token-based access\n- ✅ Data encryption"
},
{
number: "5.4",
title: "Student Submission Confirmation",
priority: "Medium",
epic: "Phase 5 - Student Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** student,\n**I want** to confirm my submission and see its status,\n**So that** I know my documents are received and what happens next.",
background: "Submission flow:\n- Final review before submit\n- Confirmation dialog\n- Success message\n- Status tracking\n- Next steps communication",
acceptance: "**Functional:**\n- ✅ Final review screen\n- ✅ Confirmation dialog\n- ✅ Success message\n- ✅ Status display\n\n**UI/UX:**\n- ✅ Clear confirmation\n- ✅ Status indicators\n- ✅ Next steps guidance\n- ✅ Thank you message\n\n**Integration:**\n- ✅ IV1: Updates submission status\n- ✅ IV2: Triggers notifications\n- ✅ IV3: Audit trail\n\n**Security:**\n- ✅ Prevent double submission\n- ✅ Confirmation required"
},
{
number: "5.5",
title: "Student Email Notifications",
priority: "Low",
epic: "Phase 5 - Student Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** student,\n**I want** to receive email notifications and reminders,\n**So that** I stay informed about my document status.",
background: "Email notifications:\n- Enrollment invitation\n- Reminder emails\n- Status updates\n- Completion confirmation\n- Unsubscribe option",
acceptance: "**Functional:**\n- ✅ Enrollment email\n- ✅ Reminder emails\n- ✅ Status updates\n- ✅ Completion email\n\n**UI/UX:**\n- ✅ Professional templates\n- ✅ Clear CTAs\n- ✅ Mobile responsive\n- ✅ Unsubscribe link\n\n**Integration:**\n- ✅ IV1: Uses existing mailer\n- ✅ IV2: Scheduled reminders\n- ✅ IV3: Tracks email events\n\n**Security:**\n- ✅ Unsubscribe mechanism\n- ✅ Email validation"
},
{
number: "6.1",
title: "Sponsor Dashboard & Bulk Signing",
priority: "High",
epic: "Phase 6 - Sponsor Portal",
effort: "4 hours",
risk: "Medium",
user_story: "**As a** sponsor,\n**I want** a dashboard to view cohorts and sign documents in bulk,\n**So that** I can efficiently complete my review responsibilities.",
background: "Sponsor dashboard:\n- Cohort list with student counts\n- Progress indicators\n- Bulk selection\n- Single signing action\n- Completion tracking",
acceptance: "**Functional:**\n- ✅ Cohort overview\n- ✅ Student counts\n- ✅ Bulk selection\n- ✅ Single signing action\n\n**UI/UX:**\n- ✅ Clean interface\n- ✅ Select all option\n- ✅ Progress bars\n- ✅ Clear signing flow\n\n**Integration:**\n- ✅ IV1: Token-based access\n- ✅ IV2: Bulk updates\n- ✅ IV3: Audit logging\n\n**Security:**\n- ✅ Token validation\n- ✅ Explicit confirmation"
},
{
number: "6.2",
title: "Sponsor Email Notifications",
priority: "Medium",
epic: "Phase 6 - Sponsor Portal",
effort: "4 hours",
risk: "Low",
user_story: "**As a** sponsor,\n**I want** email notifications for cohorts needing my signature,\n**So that** I don't miss review deadlines.",
background: "Sponsor emails:\n- New cohort notification\n- Reminder emails\n- Completion confirmation\n- Deadline alerts\n- Token-based access links",
acceptance: "**Functional:**\n- ✅ New cohort email\n- ✅ Reminder emails\n- ✅ Completion email\n- ✅ Deadline alerts\n\n**UI/UX:**\n- ✅ Professional design\n- ✅ Clear CTAs\n- ✅ Token links\n- ✅ Unsubscribe option\n\n**Integration:**\n- ✅ IV1: Scheduled reminders\n- ✅ IV2: Token generation\n- ✅ IV3: Event tracking\n\n**Security:**\n- ✅ Secure token links\n- ✅ Expiration handling"
},
{
number: "7.1",
title: "End-to-End Testing",
priority: "High",
epic: "Phase 7 - Testing & QA",
effort: "4 hours",
risk: "Low",
user_story: "**As a** QA engineer,\n**I want** comprehensive end-to-end tests,\n**So that** I can verify the complete workflow functions correctly.",
background: "E2E testing:\n- Complete cohort lifecycle\n- All three portals\n- All user roles\n- Edge cases\n- Integration points",
acceptance: "**Functional:**\n- ✅ Complete workflow tests\n- ✅ All portals covered\n- ✅ All roles tested\n- ✅ Edge cases handled\n\n**Quality:**\n- ✅ >80% test coverage\n- ✅ Automated tests\n- ✅ CI/CD integration\n- ✅ Clear test reports"
},
{
number: "7.2",
title: "Mobile Responsiveness",
priority: "Medium",
epic: "Phase 7 - Testing & QA",
effort: "4 hours",
risk: "Low",
user_story: "**As a** QA engineer,\n**I want** to test all interfaces on mobile devices,\n**So that** students and sponsors can work on any device.",
background: "Mobile testing:\n- All three portals\n- All screen sizes\n- Touch interactions\n- Performance on mobile\n- Accessibility",
acceptance: "**Functional:**\n- ✅ All portals responsive\n- ✅ Touch-friendly UI\n- ✅ Performance acceptable\n- ✅ No layout issues\n\n**Quality:**\n- ✅ Tested on real devices\n- ✅ Browser compatibility\n- ✅ Accessibility compliant"
},
{
number: "7.3",
title: "Performance Testing (50+ Students)",
priority: "High",
epic: "Phase 7 - Testing & QA",
effort: "4 hours",
risk: "Medium",
user_story: "**As a** QA engineer,\n**I want** to test performance with 50+ students,\n**So that** the system handles real-world loads.",
background: "Performance testing:\n- Load testing with 50 students\n- Bulk operations\n- Database query performance\n- API response times\n- Memory usage",
acceptance: "**Functional:**\n- ✅ 50+ students handled\n- ✅ Bulk operations < 5s\n- ✅ API < 200ms\n- ✅ No memory leaks\n\n**Quality:**\n- ✅ Performance benchmarks\n- ✅ Load test reports\n- ✅ Optimization recommendations"
},
{
number: "7.4",
title: "Security Audit",
priority: "High",
epic: "Phase 7 - Testing & QA",
effort: "4 hours",
risk: "High",
user_story: "**As a** security auditor,\n**I want** to perform security testing,\n**So that** I can identify and fix vulnerabilities.",
background: "Security testing:\n- Authentication bypass attempts\n- SQL injection tests\n- XSS vulnerability checks\n- Token security\n- Data exposure",
acceptance: "**Functional:**\n- ✅ No authentication bypass\n- ✅ No SQL injection\n- ✅ No XSS vulnerabilities\n- ✅ Secure token handling\n- ✅ No data leaks\n\n**Quality:**\n- ✅ Security report\n- ✅ Vulnerability fixes\n- ✅ OWASP compliance"
},
{
number: "7.5",
title: "User Acceptance Testing",
priority: "Medium",
epic: "Phase 7 - Testing & QA",
effort: "4 hours",
risk: "Low",
user_story: "**As a** product owner,\n**I want** user acceptance testing with real stakeholders,\n**So that** I can validate the solution meets requirements.",
background: "UAT process:\n- Real TP administrators\n- Real students\n- Real sponsors\n- Complete workflows\n- Feedback collection",
acceptance: "**Functional:**\n- ✅ All workflows validated\n- ✅ User feedback collected\n- ✅ Issues identified\n- ✅ Sign-off obtained\n\n**Quality:**\n- ✅ UAT report\n- ✅ Issue tracking\n- ✅ Final approval"
},
{
number: "8.0",
title: "Development Infrastructure",
priority: "High",
epic: "Phase 8 - Infrastructure",
effort: "4 hours",
risk: "Low",
user_story: "**As a** developer,\n**I want** local Docker infrastructure setup,\n**So that** I can develop and test FloDoc locally.",
background: "Local infrastructure:\n- Docker Compose setup\n- Database containers\n- Redis for Sidekiq\n- Local storage\n- Development configuration",
acceptance: "**Functional:**\n- ✅ Docker Compose file\n- ✅ All services running\n- ✅ Database initialized\n- ✅ Redis working\n- ✅ Development mode configured\n\n**Quality:**\n- ✅ Documentation\n- ✅ Easy setup\n- ✅ Consistent environment"
},
{
number: "8.0.1",
title: "Management Demo Readiness",
priority: "Critical",
epic: "Phase 8 - Infrastructure",
effort: "4 hours",
risk: "High",
user_story: "**As a** product owner,\n**I want** management demo scripts and validation,\n**So that** I can demonstrate the FloDoc system to stakeholders.",
background: "Demo preparation:\n- Demo scripts\n- Sample data\n- Validation checklist\n- Presentation materials\n- Success metrics",
acceptance: "**Functional:**\n- ✅ Demo scripts written\n- ✅ Sample data created\n- ✅ Validation complete\n- ✅ Presentation ready\n\n**Quality:**\n- ✅ Management approval\n- ✅ Demo walkthrough\n- ✅ Success criteria met"
},
{
number: "8.5",
title: "User Communication & Training",
priority: "Low",
epic: "Phase 8 - Infrastructure",
effort: "4 hours",
risk: "Low",
user_story: "**As a** training coordinator,\n**I want** user communication and training materials,\n**So that** users can successfully use FloDoc.",
background: "Training materials:\n- User guides\n- Video tutorials\n- FAQ documents\n- Email templates\n- Support contact info",
acceptance: "**Functional:**\n- ✅ User guides created\n- ✅ Video tutorials recorded\n- ✅ FAQ compiled\n- ✅ Support contacts listed\n\n**Quality:**\n- ✅ Clear language\n- ✅ Visual examples\n- ✅ Accessible format"
},
{
number: "8.6",
title: "In-App User Documentation",
priority: "Low",
epic: "Phase 8 - Infrastructure",
effort: "4 hours",
risk: "Low",
user_story: "**As a** user,\n**I want** in-app help and documentation,\n**So that** I can get assistance without leaving the application.",
background: "In-app help:\n- Contextual help icons\n- Tooltips\n- Help sidebar\n- Searchable documentation\n- Contact support",
acceptance: "**Functional:**\n- ✅ Help icons on all screens\n- ✅ Tooltips for complex UI\n- ✅ Searchable docs\n- ✅ Support contact\n\n**Quality:**\n- ✅ Contextual relevance\n- ✅ Easy to find help\n- ✅ Clear explanations"
},
{
number: "8.7",
title: "Knowledge Transfer & Ops Docs",
priority: "Low",
epic: "Phase 8 - Infrastructure",
effort: "4 hours",
risk: "Low",
user_story: "**As a** operations team,\n**I want** comprehensive operations documentation,\n**So that** I can maintain and support FloDoc.",
background: "Operations docs:\n- Deployment procedures\n- Backup/restore\n- Monitoring\n- Troubleshooting\n- Incident response",
acceptance: "**Functional:**\n- ✅ Deployment guide\n- ✅ Backup procedures\n- ✅ Monitoring setup\n- ✅ Troubleshooting guide\n- ✅ Incident response plan\n\n**Quality:**\n- ✅ Clear step-by-step\n- ✅ Complete coverage\n- ✅ Regular updates"
}
];
// ========================================
// KANBAN COLUMNS
// ========================================
const columns = [
{ id: 'backlog', name: 'Backlog', color: '#E6E6E6' },
{ id: 'todo', name: 'To Do', color: '#FFE74C' },
{ id: 'progress', name: 'In Progress', color: '#35A7FF' },
{ id: 'review', name: 'Review', color: '#FF5964' },
{ id: 'done', name: 'Done', color: '#83E281' }
];
// ========================================
// STATE MANAGEMENT
// ========================================
let storyStates = {};
let filteredStories = [...storiesData];
let draggedStory = null;
// ========================================
// DOM ELEMENTS
// ========================================
const kanbanBoard = document.getElementById('kanbanBoard');
const searchBox = document.getElementById('searchBox');
const saveBtn = document.getElementById('saveBtn');
const resetBtn = document.getElementById('resetBtn');
const modalOverlay = document.getElementById('modalOverlay');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
const modalClose = document.getElementById('modalClose');
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
const statTotal = document.getElementById('statTotal');
const statDone = document.getElementById('statDone');
// ========================================
// INITIALIZATION
// ========================================
function init() {
loadState();
renderBoard();
updateStats();
attachEventListeners();
}
// ========================================
// STATE PERSISTENCE
// ========================================
function loadState() {
const saved = localStorage.getItem('flodoc-kanban-state');
if (saved) {
storyStates = JSON.parse(saved);
} else {
// Initialize with default states
storiesData.forEach(story => {
storyStates[story.number] = 'backlog';
});
}
}
function saveState() {
localStorage.setItem('flodoc-kanban-state', JSON.stringify(storyStates));
showToast('Board saved successfully!', 'success');
}
function resetState() {
if (confirm('Are you sure you want to reset all stories to Backlog?')) {
storiesData.forEach(story => {
storyStates[story.number] = 'backlog';
});
saveState();
renderBoard();
updateStats();
}
}
// ========================================
// RENDERING
// ========================================
function renderBoard() {
kanbanBoard.innerHTML = '';
columns.forEach(column => {
const columnEl = createColumn(column);
kanbanBoard.appendChild(columnEl);
});
}
function createColumn(column) {
const columnEl = document.createElement('div');
columnEl.className = `kanban-column ${column.id}`;
columnEl.dataset.column = column.id;
const storiesInColumn = filteredStories.filter(
story => storyStates[story.number] === column.id
);
columnEl.innerHTML = `
<div class="column-header">
<div class="column-title">
<span>${column.name}</span>
<span class="column-count">${storiesInColumn.length}</span>
</div>
</div>
<div class="column-body" data-column="${column.id}">
${storiesInColumn.length === 0
? '<div class="empty-column">No stories here</div>'
: storiesInColumn.map(story => createStoryCard(story)).join('')
}
</div>
`;
// Add drag and drop listeners
const columnBody = columnEl.querySelector('.column-body');
columnBody.addEventListener('dragover', handleDragOver);
columnBody.addEventListener('dragleave', handleDragLeave);
columnBody.addEventListener('drop', handleDrop);
return columnEl;
}
function createStoryCard(story) {
const priorityClass = `badge-priority-${story.priority.toLowerCase()}`;
return `
<div class="story-card" draggable="true" data-number="${story.number}">
<div class="story-card-header">
<span class="story-number">${story.number}</span>
</div>
<div class="story-title">${story.title}</div>
<div class="story-meta">
<span class="badge ${priorityClass}">${story.priority}</span>
<span class="badge badge-effort">${story.effort}</span>
</div>
<div class="story-epic">${story.epic}</div>
<div class="story-preview">${story.user_story.substring(0, 100)}...</div>
<div class="story-actions">
<button class="story-action-btn" onclick="viewStory('${story.number}')">View</button>
<button class="story-action-btn" onclick="moveToNext('${story.number}')">→</button>
</div>
</div>
`;
}
// ========================================
// DRAG AND DROP
// ========================================
function handleDragStart(e) {
draggedStory = e.target.dataset.number;
e.target.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
}
function handleDragEnd(e) {
e.target.classList.remove('dragging');
draggedStory = null;
}
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
e.currentTarget.classList.add('drag-over');
}
function handleDragLeave(e) {
e.currentTarget.classList.remove('drag-over');
}
function handleDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('drag-over');
const targetColumn = e.currentTarget.dataset.column;
if (draggedStory && targetColumn) {
storyStates[draggedStory] = targetColumn;
saveState();
renderBoard();
updateStats();
showToast(`Moved story ${draggedStory} to ${targetColumn}`, 'success');
}
}
// ========================================
// STORY ACTIONS
// ========================================
function viewStory(number) {
const story = storiesData.find(s => s.number === number);
if (!story) return;
modalTitle.textContent = `Story ${story.number}: ${story.title}`;
modalBody.innerHTML = `
<div class="modal-section">
<div class="modal-section-title">User Story</div>
<div class="modal-content">${formatContent(story.user_story)}</div>
</div>
<div class="modal-section">
<div class="modal-section-title">Background</div>
<div class="modal-content">${formatContent(story.background)}</div>
</div>
<div class="modal-section">
<div class="modal-section-title">Acceptance Criteria</div>
<div class="modal-content">${formatContent(story.acceptance)}</div>
</div>
`;
modalOverlay.classList.add('active');
}
function moveToNext(number) {
const currentIndex = columns.findIndex(c => c.id === storyStates[number]);
if (currentIndex < columns.length - 1) {
const nextColumn = columns[currentIndex + 1].id;
storyStates[number] = nextColumn;
saveState();
renderBoard();
updateStats();
showToast(`Moved to ${nextColumn}`, 'success');
}
}
// ========================================
// SEARCH
// ========================================
function searchStories(query) {
const searchTerm = query.toLowerCase().trim();
if (!searchTerm) {
filteredStories = [...storiesData];
} else {
filteredStories = storiesData.filter(story =>
story.number.toLowerCase().includes(searchTerm) ||
story.title.toLowerCase().includes(searchTerm) ||
story.epic.toLowerCase().includes(searchTerm) ||
story.user_story.toLowerCase().includes(searchTerm)
);
}
renderBoard();
updateStats();
}
// ========================================
// STATS
// ========================================
function updateStats() {
const total = storiesData.length;
const done = Object.values(storyStates).filter(state => state === 'done').length;
statTotal.textContent = total;
statDone.textContent = done;
}
// ========================================
// UTILITIES
// ========================================
function formatContent(text) {
if (!text) return '';
let html = text
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/`(.+?)`/g, '<code>$1</code>')
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>');
const lines = text.split('\n');
let inList = false;
let listType = 'ul';
let result = '';
lines.forEach(line => {
if (line.match(/^[\*\-]\s+/)) {
if (!inList) {
inList = true;
result += '<ul>';
}
result += `<li>${line.replace(/^[\*\-]\s+/, '')}</li>`;
} else if (line.match(/^\d+\.\s+/)) {
if (!inList) {
inList = true;
listType = 'ol';
result += '<ol>';
}
result += `<li>${line.replace(/^\d+\.\s+/, '')}</li>`;
} else {
if (inList) {
result += `</${listType}>`;
inList = false;
listType = 'ul';
}
if (line.trim()) {
result += `<p>${line}</p>`;
}
}
});
if (inList) {
result += `</${listType}>`;
}
return result || text;
}
function showToast(message, type = 'success') {
toastMessage.textContent = message;
toast.className = `toast ${type} active`;
setTimeout(() => {
toast.classList.remove('active');
}, 3000);
}
// ========================================
// EVENT LISTENERS
// ========================================
function attachEventListeners() {
// Search
searchBox.addEventListener('input', (e) => {
searchStories(e.target.value);
});
// Save button
saveBtn.addEventListener('click', saveState);
// Reset button
resetBtn.addEventListener('click', resetState);
// Modal close
modalClose.addEventListener('click', () => {
modalOverlay.classList.remove('active');
});
modalOverlay.addEventListener('click', (e) => {
if (e.target === modalOverlay) {
modalOverlay.classList.remove('active');
}
});
// Drag events for story cards
document.addEventListener('dragstart', (e) => {
if (e.target.classList.contains('story-card')) {
handleDragStart(e);
}
});
document.addEventListener('dragend', (e) => {
if (e.target.classList.contains('story-card')) {
handleDragEnd(e);
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
modalOverlay.classList.remove('active');
}
});
}
// ========================================
// START APPLICATION
// ========================================
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>