Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/TEMPLATES/tf_project.html

O Operator
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/TEMPLATES/tf_project.html

added on local at 2026-07-01 12:35:05

Added
+255
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 13a6e0480acb
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<div class="crumbs"><a href="/projects.cgi">Projects</a><span class="sep">/</span>$proj_name</div>
2<div class="page-header">
3 <div>
4 <h1 style="display:flex;align-items:center;gap:12px"><span style="width:14px;height:14px;border-radius:4px;background:$proj_color"></span>$proj_name <span class="badge" style="font-size:12px">$proj_key</span></h1>
5 <p class="subtitle">$proj_description</p>
6 </div>
7 <div class="flex-end">
8 <a href="/kanban.cgi?project=$proj_id" class="btn" data-tip="Open the **drag-and-drop Kanban board** for this project.">Kanban</a>
9 <a href="/gantt.cgi?project=$proj_id" class="btn" data-tip="Open the **Gantt timeline**. Drag a bar to reschedule a task, drag its edges to resize.">Gantt</a>
10 <a href="/decisions.cgi?project=$proj_id" class="btn btn-ghost" data-tip="First-class decision log: why you chose what you chose.">Decisions</a>
11 <a href="/risks.cgi?project=$proj_id" class="btn btn-ghost" data-tip="Risk register with severity × likelihood heat matrix.">Risks</a>
12 <a href="/tasks_export.cgi?project=$proj_id" class="btn btn-ghost" data-tip="Download every task in this project as CSV.">Export CSV</a>
13 [if:$can_edit]<a href="/tasks_import.cgi?project=$proj_id" class="btn btn-ghost" data-tip="Bulk-create tasks from a CSV. Required column: title.">Import CSV</a>[/if]
14 [if:$can_edit]<button onclick="document.getElementById('editProjectModal').classList.add('open')" class="btn btn-ghost" data-tip="Edit this project's name, description, color, status, priority, dates, and visibility.">Edit project</button>[/if]
15 <button onclick="document.getElementById('newTaskModal').classList.add('open')" class="btn btn-primary" data-tip="Add a new task to this project. Auto-gets the next ticket number (e.g. `MOV-7`).">+ New task</button>
16 </div>
17</div>
18
19[if:$show_created]<div class="flash-msg">Project created. Add your first task to get going.</div>[/if]
20
21<div class="kpi-grid">
22 <div class="kpi"><div class="kpi-label">Total tasks</div><div class="kpi-value">$counts.total</div></div>
23 <div class="kpi success"><div class="kpi-label">Done</div><div class="kpi-value">$counts.done_n</div><div class="kpi-delta">$progress_pct% complete</div></div>
24 <div class="kpi"><div class="kpi-label">In progress</div><div class="kpi-value">$counts.prog_n</div></div>
25 <div class="kpi danger"><div class="kpi-label">Overdue</div><div class="kpi-value">$counts.overdue_n</div></div>
26</div>
27
28[if:$burndown_ok]
29<div class="card mb-3">
30 <div class="card-header" style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px">
31 <div>
32 <h3 style="margin:0">Burndown &amp; predicted completion</h3>
33 <div style="font-size:13px;margin-top:4px">
34 [if:$bd_status_good]<span style="color:#22c55e;font-weight:600">&#10003; $bd_status_label</span>[/if]
35 [if:$bd_status_bad]<span style="color:#ef4444;font-weight:600">&#9888; $bd_status_label</span>[/if]
36 [if:$bd_status_warn]<span style="color:#f59e0b;font-weight:600">$bd_status_label</span>[/if]
37 [if:$bd_predicted_date] &middot; <span class="text-muted">predicted finish $bd_predicted_date (target $proj_target_iso)</span>[/if]
38 </div>
39 </div>
40 <div style="display:flex;gap:14px;font-size:12px;flex-wrap:wrap">
41 <span><span style="display:inline-block;width:18px;height:2px;background:rgba(255,255,255,.35);vertical-align:3px"></span> Ideal</span>
42 <span><span style="display:inline-block;width:18px;height:2px;background:#5aa9ff;vertical-align:3px"></span> Actual</span>
43 [if:$bd_has_prediction]<span><span style="display:inline-block;width:18px;height:2px;background:#f59e0b;vertical-align:3px;border-top:1px dashed #f59e0b"></span> Predicted</span>[/if]
44 <span class="text-muted">$bd_remaining of $bd_total left &middot; $bd_velocity tasks/day</span>
45 </div>
46 </div>
47 <svg viewBox="0 0 $bd_chart_w $bd_chart_h" style="width:100%;height:auto;display:block">
48 [loop:@bd_y_labels]
49 <line x1="50" x2="$bd_chart_w" y1="$loop1.y" y2="$loop1.y" stroke="rgba(255,255,255,.06)" stroke-width="1"/>
50 <text x="44" y="$loop1.y" dy="4" text-anchor="end" fill="var(--col-text-dim)" font-size="11">$loop1.text</text>
51 [/loop]
52 <line x1="$bd_target_x" x2="$bd_target_x" y1="14" y2="212" stroke="#ef4444" stroke-width="1" stroke-dasharray="3,3" opacity="0.6"/>
53 <text x="$bd_target_x" y="13" text-anchor="middle" fill="#ef4444" font-size="10" font-weight="600">target</text>
54 <line x1="$bd_today_x" x2="$bd_today_x" y1="14" y2="212" stroke="rgba(255,255,255,.25)" stroke-width="1"/>
55 <polyline points="$bd_path_ideal" fill="none" stroke="rgba(255,255,255,.35)" stroke-width="1.5" stroke-dasharray="5,4"/>
56 <polyline points="$bd_path_actual" fill="none" stroke="#5aa9ff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
57 [if:$bd_has_prediction]
58 <polyline points="$bd_path_pred" fill="none" stroke="#f59e0b" stroke-width="2" stroke-dasharray="5,4" stroke-linecap="round"/>
59 <circle cx="$bd_pred_x" cy="$bd_pred_y" r="3" fill="#f59e0b"/>
60 [/if]
61 [loop:@bd_x_labels]
62 <text x="$loop1.x" y="$loop1.y" text-anchor="middle" fill="var(--col-text-dim)" font-size="11">$loop1.text</text>
63 [/loop]
64 </svg>
65</div>
66[/if]
67[if:!$burndown_ok]
68<div class="card mb-3" style="padding:18px;border-left:3px solid var(--col-warn)">
69 <strong>Burndown chart unavailable.</strong> Set both a <em>start date</em> and a <em>target due date</em> on this project, and add at least one task, and the predicted-completion chart will appear here.
70</div>
71[/if]
72
73<div class="row" style="gap:24px;align-items:stretch;flex-wrap:wrap">
74 <div class="col" style="min-width:300px;flex:2">
75 <div class="card">
76 <div class="card-header"><h3>Recent tasks</h3><a href="/kanban.cgi?project=$proj_id" class="text-muted" style="font-size:13px">Open Kanban &rarr;</a></div>
77 [if:$has_recent]
78 <table class="table">
79 <thead><tr><th>Ticket</th><th>Title</th><th>Status</th><th>Priority</th><th>Assignee</th><th>Due</th></tr></thead>
80 <tbody>
81 [loop:@recent]
82 <tr class="clickable" onclick="location.href='/task.cgi?id=$loop1.id'">
83 <td><code>$loop1.display_ticket</code></td>
84 <td>$loop1.title</td>
85 <td><span class="badge">$loop1.status</span></td>
86 <td><span class="priority-pill priority-$loop1.priority">$loop1.priority</span></td>
87 <td>[if:$loop1.assignee_name]<div style="display:inline-flex;align-items:center;gap:6px"><div class="avatar avatar-sm" style="background:$loop1.assignee_color">$loop1.assignee_initials</div><span style="font-size:12px">$loop1.assignee_name</span></div>[/if][if:!$loop1.assignee_name]<span class="text-dim">Unassigned</span>[/if]</td>
88 <td>$loop1.due_date</td>
89 </tr>
90 [/loop]
91 </tbody>
92 </table>
93 [/if]
94 [if:!$has_recent]
95 <div class="muted-empty">
96 <h3>No tasks yet</h3>
97 <p>Add your first task to start tracking work.</p>
98 <button onclick="document.getElementById('newTaskModal').classList.add('open')" class="btn btn-primary mt-2">Add first task</button>
99 </div>
100 [/if]
101 </div>
102 </div>
103 <div class="col" style="min-width:260px;flex:1">
104 <div class="card mb-3">
105 <div class="card-header"><h3>Details</h3>[if:$can_edit]<a href="/project_edit.cgi?id=$proj_id" class="text-muted" style="font-size:12px">Edit</a>[/if]</div>
106 <table class="task-meta-table">
107 <tr><td class="lbl">Status</td><td><span class="badge">$proj_status</span></td></tr>
108 <tr><td class="lbl">Priority</td><td><span class="priority-pill priority-$proj_priority">$proj_priority</span></td></tr>
109 <tr><td class="lbl">Owner</td><td>$owner_name</td></tr>
110 [if:$has_dept]<tr><td class="lbl">Department</td><td><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:$dept_color;margin-right:6px"></span>$dept_name</td></tr>[/if]
111 [if:$proj_start]<tr><td class="lbl">Start</td><td>$proj_start</td></tr>[/if]
112 [if:$proj_due]<tr><td class="lbl">Due</td><td>$proj_due</td></tr>[/if]
113 </table>
114 </div>
115 <div class="card">
116 <div class="card-header"><h3>Team</h3><a href="/project_members.cgi?id=$proj_id" class="text-muted" style="font-size:12px">Manage</a></div>
117 [if:$has_members]
118 [loop:@members]
119 <div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--col-border)">
120 <div class="avatar avatar-sm" style="background:$loop1.avatar_color">$loop1.initials</div>
121 <div style="flex:1;min-width:0"><div style="font-size:13px">$loop1.display_name</div><div style="font-size:11px;color:var(--col-text-dim)">$loop1.role</div></div>
122 </div>
123 [/loop]
124 [/if]
125 [if:!$has_members]<p class="text-dim" style="font-size:13px">No members yet.</p>[/if]
126 </div>
127 </div>
128</div>
129
130<div class="modal-backdrop" id="newTaskModal" onclick="if(event.target===this)this.classList.remove('open')">
131 <div class="modal">
132 <div class="modal-header"><h2>New task</h2><button class="modal-close" onclick="document.getElementById('newTaskModal').classList.remove('open')">&times;</button></div>
133 <form method="POST" action="/task_action.cgi">
134 <input type="hidden" name="act" value="create">
135 <input type="hidden" name="project_id" value="$proj_id">
136 <div class="modal-body">
137 <div class="field"><label data-tip="One-line summary your team will scan on Kanban cards and lists. Keep it under 80 characters.">Title *</label><input class="input" name="title" required autofocus placeholder="Short summary"></div>
138 <div class="field"><label data-tip="Full details &mdash; acceptance criteria, links, screenshots, anything a teammate needs to start. Plain text for now.">Description</label><textarea class="textarea" name="description" placeholder="Details, acceptance criteria, links"></textarea></div>
139 <div class="row">
140 <div class="col"><div class="field"><label data-tip="**Low**: nice to have. **Medium**: default. **High**: needs attention this week. **Urgent**: today. **Critical**: stop-the-world.">Priority</label>
141 <select class="select" name="priority"><option value="low">Low</option><option value="medium" selected>Medium</option><option value="high">High</option><option value="urgent">Urgent</option><option value="critical">Critical</option></select>
142 </div></div>
143 <div class="col"><div class="field"><label data-tip="When this task should be finished by. Drives Gantt bars, calendar pins, and 'due this week' / overdue counters.">Due date</label><input class="input" type="date" name="due_date"></div></div>
144 </div>
145 </div>
146 <div class="modal-footer"><button type="button" class="btn btn-ghost" onclick="document.getElementById('newTaskModal').classList.remove('open')">Cancel</button><button type="submit" class="btn btn-primary">Create task</button></div>
147 </form>
148 </div>
149</div>
150
151[if:$show_updated]<div class="flash-msg" style="background:rgba(34,197,94,.12);border-left:3px solid #22c55e;color:#22c55e;padding:10px 14px;border-radius:6px;margin:-12px 0 14px">&#10003; Project updated.</div>[/if]
152
153[if:$can_edit]
154<div class="modal-backdrop" id="editProjectModal" onclick="if(event.target===this)this.classList.remove('open')">
155 <div class="modal" style="max-width:640px">
156 <div class="modal-header"><h2>Edit project</h2><button class="modal-close" onclick="document.getElementById('editProjectModal').classList.remove('open')">&times;</button></div>
157 <form method="POST" action="/project_action.cgi">
158 <input type="hidden" name="act" value="update">
159 <input type="hidden" name="id" value="$proj_id">
160 <div class="modal-body">
161 <div class="field"><label>Name *</label><input class="input" name="name" required value="$proj_name"></div>
162 <div class="field" style="margin-top:10px"><label>Description</label><textarea class="textarea" name="description" rows="3">$proj_description</textarea></div>
163 <div class="row" style="margin-top:10px">
164 <div class="col"><div class="field"><label>Status</label>
165 <select class="select" name="status">
166 <option value="planning" $sel_status_planning>Planning</option>
167 <option value="active" $sel_status_active>Active</option>
168 <option value="on_hold" $sel_status_on_hold>On hold &mdash; paused, tasks intact</option>
169 <option value="cancelled" $sel_status_cancelled>Cancelled &mdash; abandoned, tasks intact</option>
170 <option value="complete" $sel_status_complete>Complete</option>
171 <option value="archived" $sel_status_archived>Archived</option>
172 </select>
173 </div></div>
174 <div class="col"><div class="field"><label>Priority</label>
175 <select class="select" name="priority">
176 <option value="low" $sel_priority_low>Low</option>
177 <option value="medium" $sel_priority_medium>Medium</option>
178 <option value="high" $sel_priority_high>High</option>
179 <option value="urgent" $sel_priority_urgent>Urgent</option>
180 </select>
181 </div></div>
182 <div class="col"><div class="field"><label>Color</label><input class="input" type="color" name="color" value="$proj_color" style="height:38px;padding:2px"></div></div>
183 </div>
184 <div class="row" style="margin-top:10px">
185 <div class="col"><div class="field"><label>Start date</label><input class="input" type="date" name="start_date" value="$proj_start_iso"></div></div>
186 <div class="col"><div class="field"><label>Target due date</label><input class="input" type="date" name="target_due_date" value="$proj_due_iso"></div></div>
187 </div>
188 <div class="field" style="margin-top:14px">
189 <label class="checkbox"><input type="checkbox" name="is_public_in_company" value="1" $proj_is_public> Visible to all members of the company (otherwise only project members see it)</label>
190 </div>
191 </div>
192 <div class="modal-footer">
193 <span class="text-muted" style="font-size:12px;margin-right:auto">Use the status to pause / cancel. Use the danger zone below to remove the project entirely.</span>
194 <button type="button" class="btn btn-ghost" onclick="document.getElementById('editProjectModal').classList.remove('open')">Cancel</button>
195 <button type="submit" class="btn btn-primary">Save changes</button>
196 </div>
197 </form>
198
199 <!-- ============ Danger zone ============ -->
200 <div class="pl-danger-zone">
201 <div class="pl-danger-head">
202 <div class="pl-danger-title">Danger zone</div>
203 <div class="pl-danger-sub">Two options for removing this project. Soft-delete is reversible; permanent delete is not.</div>
204 </div>
205 <div class="pl-danger-row">
206 <div class="pl-danger-info">
207 <div class="pl-danger-label">Mark as deleted</div>
208 <div class="pl-danger-desc">Hides the project and its tasks from listings, dashboards, and reports. All data is preserved &mdash; you (or any admin) can restore it from the deleted-projects list later.</div>
209 </div>
210 <form method="POST" action="/project_action.cgi" onsubmit="return confirm('Mark this project as deleted? It will be hidden from reports but recoverable.')">
211 <input type="hidden" name="act" value="mark_deleted">
212 <input type="hidden" name="id" value="$proj_id">
213 <button type="submit" class="btn pl-btn-soft">Mark deleted</button>
214 </form>
215 </div>
216 <div class="pl-danger-row pl-danger-row-hard">
217 <div class="pl-danger-info">
218 <div class="pl-danger-label">Delete permanently</div>
219 <div class="pl-danger-desc">Physically removes the project and ALL of its tasks, comments, attachments, kanban columns, and dependencies. <strong>This cannot be undone.</strong></div>
220 </div>
221 <form method="POST" action="/project_action.cgi" onsubmit="return plConfirmHardDelete(this)">
222 <input type="hidden" name="act" value="delete_permanently">
223 <input type="hidden" name="id" value="$proj_id">
224 <input type="hidden" name="confirm" value="">
225 <button type="submit" class="btn pl-btn-hard">Delete permanently</button>
226 </form>
227 </div>
228 </div>
229
230 </div>
231</div>
232<script>
233function plConfirmHardDelete(form){
234 var c = prompt("Type DELETE in caps to permanently delete this project and all of its tasks. This cannot be undone.");
235 if (c !== "DELETE") return false;
236 form.confirm.value = "YES_DELETE_PERMANENTLY";
237 return true;
238}
239</script>
240<style>
241.pl-danger-zone { margin: 18px 22px 22px; padding: 16px 18px; background: linear-gradient(160deg, rgba(239,68,68,0.06), rgba(239,68,68,0.02)); border: 1px solid rgba(239,68,68,0.28); border-radius: 12px; }
242.pl-danger-head { padding-bottom: 12px; margin-bottom: 14px; border-bottom: 1px solid rgba(239,68,68,0.18); }
243.pl-danger-title { color: #f87171; font-weight: 700; font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; }
244.pl-danger-sub { color: #8893a4; font-size: 12px; margin-top: 4px; }
245.pl-danger-row { display: flex; gap: 16px; align-items: center; padding: 12px 0; border-bottom: 1px dashed rgba(255,255,255,0.05); }
246.pl-danger-row:last-child { border-bottom: 0; }
247.pl-danger-info { flex: 1; min-width: 0; }
248.pl-danger-label { color: #fff; font-weight: 700; font-size: 13px; }
249.pl-danger-desc { color: #8893a4; font-size: 12px; margin-top: 4px; line-height: 1.5; }
250.pl-btn-soft { background: rgba(239,68,68,0.16); color: #f87171; border: 1px solid rgba(239,68,68,0.42); font-weight: 600; padding: 7px 14px; border-radius: 7px; cursor: pointer; flex-shrink: 0; }
251.pl-btn-soft:hover { background: rgba(239,68,68,0.28); }
252.pl-btn-hard { background: #b91c1c; color: #fff; border: 1px solid #ef4444; font-weight: 700; padding: 7px 14px; border-radius: 7px; cursor: pointer; flex-shrink: 0; }
253.pl-btn-hard:hover { background: #dc2626; }
254</style>
255[/if]