- Updated the status component template to include a new trigger for task status checks. - Modified the status component's Go implementation to reflect the new trigger in the HTML output. - Improved the UpdateIssue handler to conditionally set the assignee based on form input. - Enhanced the dashboard template to include a new dependencies section for issues, allowing users to add dependencies dynamically. - Refactored the dashboard's issue rows to utilize JavaScript functions for rendering status, type, and priority badges, improving maintainability and readability.
134 lines
5.7 KiB
Plaintext
134 lines
5.7 KiB
Plaintext
package components
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/LazyBachelor/LazyPM/internal/models"
|
|
)
|
|
|
|
type IssueDetailProps struct {
|
|
Issue *models.Issue
|
|
From string // "board" when navigating from board view; passed via hx-vals to child requests
|
|
}
|
|
|
|
templ IssueDetail(props IssueDetailProps) {
|
|
<div
|
|
if props.From == "board" {
|
|
hx-vals='{"from": "board"}'
|
|
}
|
|
>
|
|
<h2 class="text-2xl font-bold m-2">
|
|
{ props.Issue.Title }
|
|
</h2>
|
|
<div class="flex gap-2 flex-wrap">
|
|
@StatusBadge(props.Issue.Status)
|
|
@TypeBadge(props.Issue.IssueType)
|
|
@PriorityBadge(props.Issue.Priority)
|
|
</div>
|
|
if props.Issue.Description != "" {
|
|
<div class="mt-4">
|
|
<h3 class="text-sm font-semibold opacity-70 mb-1">Description</h3>
|
|
<p class="whitespace-pre-wrap text-sm">{ props.Issue.Description }</p>
|
|
</div>
|
|
}
|
|
<div class="mt-4">
|
|
<h3 class="text-sm font-semibold opacity-70 mb-1">Dependencies</h3>
|
|
<div
|
|
id="issue-dependencies-detail"
|
|
hx-get={ "/issues/" + props.Issue.ID + "/dependencies?container_id=issue-dependencies-detail" }
|
|
hx-trigger="load"
|
|
></div>
|
|
</div>
|
|
<div class="divider my-2"></div>
|
|
<div class="grid grid-cols-2 gap-2 text-sm">
|
|
<div class="bg-base-200 rounded-lg p-2">
|
|
<span class="text-xs opacity-70 block">Created</span>
|
|
<p>{ props.Issue.CreatedAt.Format("Jan 2, 2006") }</p>
|
|
</div>
|
|
<div class="bg-base-200 rounded-lg p-2">
|
|
<span class="text-xs opacity-70 block">Updated</span>
|
|
<p>{ props.Issue.UpdatedAt.Format("Jan 2, 2006") }</p>
|
|
</div>
|
|
<div class="flex flex-row gap-1 bg-base-200 rounded-lg p-2">
|
|
<span class="text-xs opacity-70 block">Created by</span>
|
|
<p class="text-xs opacity-70">{ props.Issue.CreatedBy }</p>
|
|
</div>
|
|
<a
|
|
class="btn btn-primary btn-sm flex flex-row gap-1 items-center justify-center"
|
|
hx-get={ "/issues/" + props.Issue.ID + "/assignee" }
|
|
hx-target="#modal-container"
|
|
hx-swap="innerHTML"
|
|
>
|
|
if props.Issue.Assignee == "" {
|
|
<span>Unassigned (click to assign) </span>
|
|
} else {
|
|
<span>Assignee: { props.Issue.Assignee }</span>
|
|
}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
templ StatusBadge(status models.Status) {
|
|
switch status {
|
|
case "open":
|
|
<span class="badge badge-info badge-sm whitespace-nowrap">Open</span>
|
|
case "in_progress":
|
|
<span class="badge badge-warning badge-sm whitespace-nowrap">In Progress</span>
|
|
case "closed":
|
|
<span class="badge badge-success badge-sm whitespace-nowrap">Closed</span>
|
|
case "blocked":
|
|
<span class="badge badge-error badge-sm whitespace-nowrap">Blocked</span>
|
|
case "deferred":
|
|
<span class="badge badge-ghost badge-sm whitespace-nowrap">Deferred</span>
|
|
default:
|
|
<span class="badge badge-ghost badge-sm whitespace-nowrap">{ string(status) }</span>
|
|
}
|
|
}
|
|
|
|
templ TypeBadge(issueType models.IssueType) {
|
|
switch issueType {
|
|
case "bug":
|
|
<span class="badge badge-outline badge-error badge-sm whitespace-nowrap">Bug</span>
|
|
case "feature":
|
|
<span class="badge badge-outline badge-success badge-sm whitespace-nowrap">Feature</span>
|
|
case "task":
|
|
<span class="badge badge-outline badge-info badge-sm whitespace-nowrap">Task</span>
|
|
case "chore":
|
|
<span class="badge badge-outline badge-warning badge-sm whitespace-nowrap">Chore</span>
|
|
case "epic":
|
|
<span class="badge badge-outline badge-secondary badge-sm whitespace-nowrap">Epic</span>
|
|
default:
|
|
<span class="badge badge-outline badge-sm whitespace-nowrap">{ string(issueType) }</span>
|
|
}
|
|
}
|
|
|
|
templ PriorityBadge(priority int) {
|
|
switch priority {
|
|
case 0:
|
|
<span class="badge badge-ghost badge-sm whitespace-nowrap">Irrelevant</span>
|
|
case 1:
|
|
<span class="badge badge-ghost badge-sm whitespace-nowrap">Low</span>
|
|
case 2:
|
|
<span class="badge badge-info badge-sm whitespace-nowrap">Normal</span>
|
|
case 3:
|
|
<span class="badge badge-warning badge-sm whitespace-nowrap">High</span>
|
|
case 4:
|
|
<span class="badge badge-error badge-sm whitespace-nowrap">Critical</span>
|
|
default:
|
|
<span class="badge badge-ghost badge-sm whitespace-nowrap">{ fmt.Sprintf("P%d", priority) }</span>
|
|
}
|
|
}
|
|
|
|
templ StatusBadgeExpr(statusExpr string, fallbackStatus string) {
|
|
<span x-html={ fmt.Sprintf(`'<span class="badge ${(() => { const s = %s || %q; switch(s) { case "open": return "badge-info"; case "in_progress": return "badge-warning"; case "closed": return "badge-success"; case "blocked": return "badge-error"; default: return "badge-ghost"; } })()} badge-sm whitespace-nowrap">' + ((%s || %q).replace(/_/g, " ").replace(/\\b\\w/g, l => l.toUpperCase())) + '</span>'`, statusExpr, fallbackStatus, statusExpr, fallbackStatus) }></span>
|
|
}
|
|
|
|
templ TypeBadgeExpr(typeExpr string, fallbackType string) {
|
|
<span x-html={ fmt.Sprintf(`'<span class="badge badge-outline ${(() => { const t = %s || %q; switch(t) { case "bug": return "badge-error"; case "feature": return "badge-success"; case "task": return "badge-info"; case "chore": return "badge-warning"; case "epic": return "badge-secondary"; default: return ""; } })()} badge-sm whitespace-nowrap">' + ((%s || %q).charAt(0).toUpperCase() + (%s || %q).slice(1)) + '</span>'`, typeExpr, fallbackType, typeExpr, fallbackType, typeExpr, fallbackType) }></span>
|
|
}
|
|
|
|
templ PriorityBadgeExpr(priorityExpr string, fallbackPriority int) {
|
|
<span x-html={ fmt.Sprintf(`'<span class="badge ${(() => { const p = parseInt(%s || %d); switch(p) { case 0: case 1: return "badge-ghost"; case 2: return "badge-info"; case 3: return "badge-warning"; case 4: return "badge-error"; default: return "badge-ghost"; } })()} badge-sm whitespace-nowrap">' + (() => { const p = parseInt(%s || %d); switch(p) { case 0: return "Irrelevant"; case 1: return "Low"; case 2: return "Normal"; case 3: return "High"; case 4: return "Critical"; default: return "P" + p; } })() + '</span>'`, priorityExpr, fallbackPriority, priorityExpr, fallbackPriority) }></span>
|
|
}
|