feature: Initial MVP

This commit is contained in:
Keith Solomon
2026-04-05 16:20:39 -05:00
parent 3af0b9cd0f
commit 812e5c2f2a
60 changed files with 5917 additions and 5 deletions
+228
View File
@@ -0,0 +1,228 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Main application entrypoint.
*/
declare(strict_types=1);
require_once dirname(__DIR__) . '/bootstrap.php';
use IronKanban\Repository\ProjectRepository;
use IronKanban\Service\BoardService;
$projectRepository = new ProjectRepository();
$projects = $projectRepository->getAll();
$requestedProject = isset($_GET['project']) ? trim((string) $_GET['project']) : '';
$activeProjectId = $requestedProject !== '' ? $requestedProject : ($projects[0]->id ?? '');
$boardService = new BoardService();
$initialState = null;
if ($activeProjectId !== '') {
try {
$initialState = $boardService->getBoardState($activeProjectId);
} catch (Throwable $exception) {
$initialState = null;
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="<?php echo e(csrf_token()); ?>">
<title><?php echo e((string) config('app_name')); ?></title>
<link rel="stylesheet" href="/assets/app.css">
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<div>
<p class="eyebrow">Git-backed markdown kanban</p>
<h1>IronKanban</h1>
<p class="sidebar-copy">Projects stay as flat files, while the UI gives you drag-and-drop columns, quick edits, notes, and polling-based refreshes.</p>
</div>
<section class="project-list">
<div class="section-heading">
<h2>Projects</h2>
<span><?php echo count($projects); ?></span>
</div>
<?php if ($projects === []) : ?>
<div class="empty-state compact">
<p>No projects found in <code><?php echo e(project_root()); ?></code>.</p>
</div>
<?php else : ?>
<?php foreach ($projects as $project) : ?>
<a class="project-link<?php echo $project->id === $activeProjectId ? ' active' : ''; ?>" href="/?project=<?php echo rawurlencode($project->id); ?>">
<strong><?php echo e($project->title); ?></strong>
<span><?php echo e($project->id); ?></span>
</a>
<?php endforeach; ?>
<?php endif; ?>
</section>
<section class="project-root">
<div class="section-heading">
<h2>Storage</h2>
</div>
<p><code><?php echo e(project_root()); ?></code></p>
</section>
</aside>
<main class="workspace" id="app" data-project-id="<?php echo e($activeProjectId); ?>">
<?php if ($initialState === null) : ?>
<section class="hero empty-state">
<h2>No project selected</h2>
<p>Create or copy a project folder into <code><?php echo e(project_root()); ?></code> using the markdown layout from the notes.</p>
</section>
<?php else : ?>
<header class="workspace-header">
<div>
<p class="eyebrow">Project Board</p>
<h2><?php echo e((string) $initialState['project']['title']); ?></h2>
<p class="project-description"><?php echo nl2br(e((string) $initialState['project']['body'])); ?></p>
</div>
<div class="header-actions">
<button type="button" class="button ghost" data-action="new-column">New Column</button>
<button type="button" class="button primary" data-action="new-task">New Task</button>
</div>
</header>
<section class="workspace-stack">
<section class="notes-panel is-collapsible is-collapsed" id="notes-panel" data-collapsible>
<div class="notes-panel-header">
<div>
<p class="eyebrow">Project Notes</p>
<h2>Notes</h2>
</div>
<div class="notes-panel-actions">
<button type="button" class="button ghost compact" data-action="new-note">Add Note</button>
<button
type="button"
class="button ghost compact collapse-toggle"
data-action="toggle-notes"
data-target="notes-list"
aria-expanded="false"
aria-controls="notes-list"
>
Show Notes
</button>
</div>
</div>
<div id="notes-list" class="notes-list" hidden></div>
</section>
<section class="board-panel">
<div class="board-scroll">
<div class="board-columns" id="board-columns"></div>
</div>
</section>
<section class="trash-panel" id="trash-panel">
<div class="trash-panel-header">
<div>
<p class="eyebrow">Discarded Tasks</p>
<h2>Trash</h2>
</div>
<button
type="button"
class="button ghost compact collapse-toggle"
data-action="toggle-trash"
aria-expanded="false"
aria-controls="trash-columns-wrap"
>
Show Trash
</button>
</div>
<div class="trash-dropzone" id="trash-dropzone" data-column-id="trash">
<p class="trash-dropzone-title">Drop tasks here to discard them</p>
<p class="trash-dropzone-meta" id="trash-dropzone-meta">Trash is empty.</p>
</div>
<div id="trash-columns-wrap" class="trash-columns-wrap" hidden>
<div class="board-scroll">
<div class="board-columns board-columns-trash" id="trash-columns"></div>
</div>
</div>
</section>
</section>
<?php endif; ?>
</main>
</div>
<dialog id="task-dialog" class="dialog">
<form method="dialog" class="dialog-card" id="task-form">
<div class="dialog-header">
<h3 id="task-dialog-title">Task</h3>
<button type="submit" class="icon-button" aria-label="Close">×</button>
</div>
<input type="hidden" name="id">
<label>
<span>Title</span>
<input type="text" name="title" maxlength="200" required>
</label>
<label>
<span>Column</span>
<select name="column"></select>
</label>
<label>
<span>Priority</span>
<select name="priority">
<option value="low">Low</option>
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</label>
<label class="checkbox-row">
<input type="checkbox" name="completed">
<span>Completed</span>
</label>
<label class="checkbox-row">
<input type="checkbox" name="is_active" checked>
<span>Active</span>
</label>
<label>
<span>Markdown</span>
<textarea name="body" rows="12"></textarea>
</label>
<div class="dialog-actions">
<button type="button" class="button ghost" data-action="trash-task">Move To Trash</button>
<button type="submit" class="button primary">Save Task</button>
</div>
</form>
</dialog>
<dialog id="note-dialog" class="dialog">
<form method="dialog" class="dialog-card" id="note-form">
<div class="dialog-header">
<h3 id="note-dialog-title">Note</h3>
<button type="submit" class="icon-button" aria-label="Close">×</button>
</div>
<input type="hidden" name="id">
<label>
<span>Title</span>
<input type="text" name="title" maxlength="200" required>
</label>
<label>
<span>Markdown</span>
<textarea name="body" rows="14"></textarea>
</label>
<div class="dialog-actions">
<button type="submit" class="button primary">Save Note</button>
</div>
</form>
</dialog>
<script id="initial-state" type="application/json"><?php echo
$initialState !== null
? (string) json_encode(
$initialState,
JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
)
: 'null';
?></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
<script src="/assets/app.js"></script>
</body>
</html>