150 lines
6.7 KiB
HTML
150 lines
6.7 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">← 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">📌</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}} · {{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">· {{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" id="comment-form">
|
|
<input type="hidden" name="gorilla.csrf.Token" value="{{$.CSRFToken}}">
|
|
<div id="comment-drop-zone" class="rounded-md border-2 border-dashed border-gray-300 transition-colors">
|
|
<textarea name="body" id="body" rows="3"
|
|
placeholder="Add a comment... (Markdown supported)"
|
|
class="block w-full border-0 rounded-t-md px-3 py-2 focus:ring-0 focus:outline-none bg-transparent"></textarea>
|
|
<div class="border-t border-gray-200 px-3 py-2 text-xs text-gray-400">
|
|
Drop files, paste images, or
|
|
<label for="comment-attachments" class="cursor-pointer text-blue-600 hover:text-blue-500 font-medium">browse</label>
|
|
<input type="file" name="attachments" id="comment-attachments" multiple class="hidden">
|
|
</div>
|
|
</div>
|
|
<div id="comment-file-list" class="mt-2 space-y-1 hidden"></div>
|
|
<div class="mt-3 flex justify-end">
|
|
<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 src="/static/js/attachments.js"></script>
|
|
<script>
|
|
initAttachmentZone({
|
|
formEl: document.getElementById('comment-form'),
|
|
fileInput: document.getElementById('comment-attachments'),
|
|
dropZone: document.getElementById('comment-drop-zone'),
|
|
fileListEl: document.getElementById('comment-file-list'),
|
|
pasteTarget: document.getElementById('body'),
|
|
});
|
|
</script>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|