forgejo-tickets/web/templates/pages/tickets/detail.html

148 lines
6.4 KiB
HTML

{{define "title"}}Ticket Detail{{end}}
{{define "content"}}
{{with .Data}}
<div class="mb-4">
<a href="/tickets" class="text-sm text-blue-600 hover:text-blue-500">&larr; Back to tickets</a>
</div>
<div class="bg-white p-6 rounded-lg shadow ring-1 ring-gray-200">
<div class="flex items-start justify-between">
<div>
<div class="flex items-center gap-2">
{{if .Ticket.Pinned}}<span class="text-gray-400" title="Pinned">&#128204;</span>{{end}}
<h1 class="text-xl font-bold text-gray-900">{{.Ticket.Title}}</h1>
</div>
<p class="mt-1 text-sm text-gray-500">
{{if .Repo}}{{.Repo.Name}} &middot; {{end}}
Created {{formatDate .Ticket.CreatedAt}}
</p>
</div>
<div class="flex items-center gap-2">
{{if .Ticket.Priority}}{{priorityBadge (print .Ticket.Priority)}}{{end}}
{{statusBadge (print .Ticket.Status)}}
</div>
</div>
<!-- Metadata -->
<div class="mt-4 flex flex-wrap gap-x-6 gap-y-1 text-sm text-gray-600">
{{if .Ticket.Assignees}}
<div>Assigned to: <span class="font-medium text-gray-900">{{.Ticket.Assignees}}</span></div>
{{end}}
{{if .Ticket.DueDate}}
<div>
Due: <span class="font-medium {{if isOverdue .Ticket.DueDate}}text-red-600{{else}}text-gray-900{{end}}">{{formatDatePtr .Ticket.DueDate}}</span>
{{if isOverdue .Ticket.DueDate}}<span class="text-xs text-red-600 font-medium">(overdue)</span>{{end}}
</div>
{{end}}
</div>
<div class="mt-6 prose prose-sm max-w-none text-gray-700">
{{renderMarkdown .Ticket.Description .Mentions}}
</div>
<!-- Issue Attachments -->
{{if .Ticket.Attachments}}
<div class="mt-4 border-t border-gray-200 pt-4">
<h3 class="text-sm font-medium text-gray-700 mb-2">Attachments</h3>
<div class="flex flex-wrap gap-2">
{{range .Ticket.Attachments}}
<a href="/tickets/{{$.Data.Ticket.ID}}/assets/{{.ID}}/{{.Name}}" class="inline-flex items-center gap-1 rounded-md bg-gray-100 px-2.5 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200">
{{.Name}} <span class="text-gray-400">({{.Size}} bytes)</span>
</a>
{{end}}
</div>
</div>
{{end}}
</div>
<!-- Related Tickets -->
{{if .RelatedIssues}}
<div class="mt-6 bg-white p-4 rounded-lg shadow ring-1 ring-gray-200">
<h2 class="text-sm font-semibold text-gray-900 mb-2">Related Tickets</h2>
<ul class="space-y-1">
{{range .RelatedIssues}}
<li class="text-sm">
{{if .IsVisible}}
<a href="/tickets/{{.TicketID}}" class="text-blue-600 hover:text-blue-500">{{.DisplayText}}</a>
{{else}}
<span class="text-gray-400">{{.DisplayText}}</span>
{{end}}
</li>
{{end}}
</ul>
</div>
{{end}}
<!-- Timeline -->
<div class="mt-8">
<h2 class="text-lg font-semibold text-gray-900 mb-4">Activity</h2>
{{if .Timeline}}
<div class="space-y-4">
{{range .Timeline}}
{{if eq .Type "comment"}}
<div class="{{if .IsTeam}}bg-blue-50 ring-blue-200{{else}}bg-white ring-gray-200{{end}} p-4 rounded-lg shadow ring-1">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-gray-900">{{.AuthorName}}</span>
{{if .IsTeam}}<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">Team</span>{{end}}
</div>
<span class="text-xs text-gray-500">{{formatDateTime .CreatedAt}}</span>
</div>
<div class="text-sm text-gray-700 prose prose-sm max-w-none">{{renderMarkdown .Body $.Data.Mentions}}</div>
{{if .Attachments}}
{{$commentID := .CommentID}}
<div class="mt-2 flex flex-wrap gap-2">
{{range .Attachments}}
<a href="/tickets/{{$.Data.Ticket.ID}}/comments/{{$commentID}}/assets/{{.ID}}/{{.Name}}" class="inline-flex items-center gap-1 rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 hover:bg-gray-200">
{{.Name}}
</a>
{{end}}
</div>
{{end}}
</div>
{{else}}
<div class="flex items-center gap-2 py-2 px-4 text-sm text-gray-500">
<span class="inline-block w-2 h-2 rounded-full {{if eq .Type "status_change"}}bg-yellow-400{{else}}bg-gray-300{{end}}"></span>
<span class="font-medium text-gray-700">{{.AuthorName}}</span>
<span>{{.EventText}}</span>
<span class="text-xs">&middot; {{formatDateTime .CreatedAt}}</span>
</div>
{{end}}
{{end}}
</div>
{{else}}
<p class="text-sm text-gray-500">No activity yet.</p>
{{end}}
<!-- Add Comment -->
<form method="POST" action="/tickets/{{.Ticket.ID}}/comments" enctype="multipart/form-data" class="mt-6">
<input type="hidden" name="gorilla.csrf.Token" value="{{$.CSRFToken}}">
<div>
<label for="body" class="sr-only">Add a comment</label>
<textarea name="body" id="body" rows="3"
placeholder="Add a comment... (Markdown supported)"
class="block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"></textarea>
</div>
<div class="mt-3 flex items-center justify-between">
<div>
<label for="attachments" class="text-sm text-gray-500 cursor-pointer hover:text-gray-700">
Attach files
<input type="file" name="attachments" id="attachments" multiple class="hidden">
</label>
<span id="file-count" class="text-xs text-gray-400 ml-2"></span>
</div>
<button type="submit" class="rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow hover:bg-blue-500">Add Comment</button>
</div>
</form>
<script>
document.getElementById('attachments').addEventListener('change', function() {
var count = this.files.length;
document.getElementById('file-count').textContent = count > 0 ? count + ' file(s) selected' : '';
});
</script>
</div>
{{end}}
{{end}}