ความคืบหน้างาน ไฟล์ต้นฉบับ

tracker/templates/partials.html

{{define "legend"}}
<ul class="legend" aria-label="ความหมายของสี">
  <li><span class="sw sw-todo" aria-hidden="true"></span>Todo</li>
  <li><span class="sw sw-in_progress" aria-hidden="true"></span>In Progress</li>
  <li><span class="sw sw-done" aria-hidden="true"></span>Done</li>
  <li><span class="sw sw-review" aria-hidden="true"></span>Wait for review</li>
  <li><span class="sw sw-reviewed" aria-hidden="true"></span>Reviewed</li>
</ul>
{{end}}

{{/* One task segment. */}}
{{define "seg"}}<li class="seg seg-{{.Status}}" title="{{.ID}} {{.Title}} ({{statusLabel .Status}})"><span class="sr">{{.ID}} {{.Title}}: {{statusLabel .Status}}</span></li>{{end}}

{{/* Segments of one epic. */}}
{{define "epicsegs"}}{{if .Tasks}}<ol class="segs" style="flex:{{len .Tasks}}" aria-label="{{.Code}} {{.Title}}: Reviewed {{.Counts.Reviewed}}, Done {{.Counts.Done}}, กำลังทำ {{.Counts.InProgress}}, รอรีวิว {{.Counts.Review}}, ยังไม่เริ่ม {{.Counts.Todo}}">{{range .Tasks}}{{template "seg" .}}{{end}}</ol>{{end}}{{end}}

{{/* One milestone as a lane: lane number, one bar grouped by epic, and a row per epic. */}}
{{define "lane"}}{{$mid := .ID}}
<div class="lane">
  <span class="lane-no" aria-label="Milestone {{.No}}">{{.No}}</span>
  <div class="lane-body">
    <div class="lane-head">
      <h3 class="lane-title"><a href="{{milestoneHref .ID}}">{{.Title}}</a></h3>
      <span class="lane-meta">{{.Counts.Complete}}/{{.Counts.Total}} task เสร็จ ใน {{len .Epics}} epic{{if .Target}} <span class="lane-target">เป้าหมาย {{thaiDate .Target}}</span>{{end}}</span>
    </div>
    {{if .Counts.Total}}
    <div class="segs-grouped">{{range .Epics}}{{template "epicsegs" .}}{{end}}</div>
    {{else}}
    <p class="lane-empty">ยังไม่มีงานใน milestone นี้</p>
    {{end}}
    {{if .Epics}}
    <ul class="epic-rows" aria-label="Epic ใน Milestone {{.No}}">
      {{range .Epics}}
      <li class="epic-row">
        <span class="epic-code">{{.Code}}</span>
        <a class="epic-name" href="{{epicHref $mid .ID}}">{{.Title}}</a>
        <span class="epic-bar">{{if .Tasks}}{{template "epicsegs" .}}{{end}}</span>
        <span class="epic-meta">{{.Counts.Complete}}/{{.Counts.Total}}</span>
      </li>
      {{end}}
    </ul>
    {{end}}
  </div>
  <span class="lane-pct">{{.Counts.Percent}}%</span>
</div>
{{end}}

{{/* Breadcrumb for a task: Milestone, epic, task id. */}}
{{define "where"}}<a href="{{milestoneHref .MilestoneID}}">Milestone {{.MilestoneNo}}</a> › <a href="{{epicHref .MilestoneID .EpicID}}">{{.EpicCode}} {{.EpicTitle}}</a>{{end}}

{{/* Breadcrumb trail for the milestone, epic and task pages. */}}
{{define "crumbs"}}{{if .}}<nav class="crumbs" aria-label="ตำแหน่งของหน้า"><ol>{{range .}}<li>{{if .Href}}<a href="{{.Href}}">{{.Label}}</a>{{else}}<span aria-current="page">{{.Label}}</span>{{end}}</li>{{end}}</ol></nav>{{end}}{{end}}

{{/* Status mix of a milestone or epic, as text. */}}
{{define "countline"}}<span class="countline"><span>Reviewed {{.Reviewed}}</span><span>Done {{.Done}}</span><span>In Progress {{.InProgress}}</span><span>Wait for review {{.Review}}</span><span>Todo {{.Todo}}</span></span>{{end}}

{{define "chip"}}<span class="chip chip-{{.}}">{{statusLabel .}}</span>{{end}}

{{/* Attached files: repo paths open in the file viewer in a new tab. */}}
{{define "refs"}}{{if .}}<ul class="refs">{{range .}}{{$h := refHref .}}<li>{{if $h}}<a class="ref-link" href="{{$h}}" target="_blank" rel="noopener"><code>{{.}}</code><span class="ref-ext" aria-hidden="true">↗</span><span class="sr"> (เปิดในแท็บใหม่)</span></a>{{else}}<code>{{.}}</code>{{end}}</li>{{end}}</ul>{{end}}{{end}}

{{/* Review details: state, reviewer, what to check, numbered checklist, decision. Used by the review page and the task page. */}}
{{define "reviewbody"}}{{with .Review}}{{$st := .StateOrTodo}}
<dl class="review-meta">
  <div><dt>สถานะรีวิว</dt><dd><span class="rs rs-{{$st}}" aria-hidden="true"></span>{{reviewState $st}}</dd></div>
  {{if .Reviewer}}<div><dt>ผู้รีวิว</dt><dd>{{.Reviewer}}</dd></div>{{end}}
  {{if .RequestedAt}}<div><dt>ส่งรีวิวเมื่อ</dt><dd>{{thaiDate .RequestedAt}}</dd></div>{{end}}
  {{if .DecidedAt}}<div><dt>ตัดสินเมื่อ</dt><dd>{{thaiDate .DecidedAt}}{{if .DecidedBy}} โดย {{.DecidedBy}}{{end}}</dd></div>{{end}}
  {{if $.Owner}}<div><dt>ผู้ทำ</dt><dd>{{$.Owner}}</dd></div>{{end}}
</dl>
{{if .Decision}}<div class="decision decision-{{$st}}"><p class="decision-label">ผลการรีวิว</p><div class="md">{{md .Decision}}</div></div>{{end}}
{{if .Focus}}<div class="review-focus"><p class="decision-label">สิ่งที่ต้องตรวจ</p><div class="md">{{md .Focus}}</div></div>{{end}}
{{if .History}}
<details class="history">
  <summary>ประวัติการรีวิว ({{len .History}})</summary>
  <ol class="history-list">
    {{range .History}}<li><p class="history-meta"><span class="rs rs-{{.State}}" aria-hidden="true"></span><b>{{reviewState .State}}</b>{{if .By}} โดย {{.By}}{{end}}{{if .At}} <time>{{thaiDate .At}}</time>{{end}}</p>{{if .Decision}}<div class="md">{{md .Decision}}</div>{{end}}</li>{{end}}
  </ol>
</details>
{{end}}
{{end}}
{{end}}

{{/* Review form: checklist ticks, Approve / Reject / Hold, result text (Markdown), reviewer name, Save. */}}
{{define "reviewform"}}
<form class="review-form" method="post" action="/reviews/{{.TaskID}}" novalidate>
  <input type="hidden" name="return" value="{{.Return}}">
  {{if .Error}}<p class="form-error" role="alert">{{.Error}}</p>{{end}}
  {{if .Items}}
  <fieldset class="checklist">
    <legend class="checklist-head"><b>Checklist สำหรับผู้รีวิว</b> <span class="muted">ติ๊กขั้นที่ทำแล้ว</span></legend>
    <ol class="checklist-steps">
      {{range .Items}}<li><input type="checkbox" class="cl-input" id="cl-{{$.TaskID}}-{{.Index}}" name="checklist" value="{{.Index}}"{{if .Checked}} checked{{end}}><label for="cl-{{$.TaskID}}-{{.Index}}" class="md">{{md .Text}}</label></li>{{end}}
    </ol>
  </fieldset>
  {{end}}
  <fieldset class="outcome">
    <legend>ผลการรีวิว</legend>
    <div class="outcome-options">
      <label class="oc oc-approve"><input type="radio" name="outcome" value="approve"{{if eq .Outcome "approve"}} checked{{end}}><span>Approve</span></label>
      <label class="oc oc-reject"><input type="radio" name="outcome" value="reject"{{if eq .Outcome "reject"}} checked{{end}}><span>Reject</span></label>
      <label class="oc oc-hold"><input type="radio" name="outcome" value="hold"{{if eq .Outcome "hold"}} checked{{end}}><span>Hold</span></label>
    </div>
  </fieldset>
  <label class="field" for="decision-{{.TaskID}}">
    <span class="field-label">รายละเอียดผลการรีวิว <span class="muted">(Markdown ได้ บังคับกรอกเมื่อ Reject หรือ Hold)</span></span>
    <textarea id="decision-{{.TaskID}}" name="decision" rows="5" placeholder="เช่น **ต้องแก้** ข้อ 2: รวม FIN_OFFICER กับ FIN_HEAD เป็นบทบาทเดียว">{{.Decision}}</textarea>
  </label>
  <div class="form-row">
    <label class="field field-name" for="reviewer-{{.TaskID}}">
      <span class="field-label">ชื่อผู้รีวิว</span>
      <input id="reviewer-{{.TaskID}}" name="reviewer" type="text" maxlength="100" value="{{.Reviewer}}" autocomplete="name">
    </label>
    <button type="submit" class="btn-save">บันทึกผลการรีวิว</button>
  </div>
</form>
{{end}}