hr_lite 0.54.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/app/assets/javascripts/hr_lite/confirm.js +9 -5
  4. data/app/assets/javascripts/hr_lite/countdown.js +9 -2
  5. data/app/assets/javascripts/hr_lite/search.js +1 -1
  6. data/app/assets/stylesheets/hr_lite/hr_lite.css +18 -9
  7. data/app/controllers/hr_lite/admin/attendances_controller.rb +10 -3
  8. data/app/controllers/hr_lite/admin/audit_logs_controller.rb +1 -0
  9. data/app/controllers/hr_lite/admin/employees_controller.rb +11 -4
  10. data/app/controllers/hr_lite/admin/expenses_controller.rb +4 -0
  11. data/app/controllers/hr_lite/admin/hr_requests_controller.rb +4 -0
  12. data/app/controllers/hr_lite/admin/leave_requests_controller.rb +4 -1
  13. data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +9 -5
  14. data/app/controllers/hr_lite/admin/reports_controller.rb +3 -2
  15. data/app/controllers/hr_lite/admin/salary_structures_controller.rb +2 -0
  16. data/app/controllers/hr_lite/admin/tax_declarations_controller.rb +13 -3
  17. data/app/controllers/hr_lite/approvals_controller.rb +1 -0
  18. data/app/controllers/hr_lite/home_controller.rb +2 -2
  19. data/app/controllers/hr_lite/regularization_requests_controller.rb +4 -4
  20. data/app/controllers/hr_lite/tax_declarations_controller.rb +9 -5
  21. data/app/helpers/hr_lite/application_helper.rb +14 -8
  22. data/app/jobs/hr_lite/attendance_close_job.rb +1 -1
  23. data/app/jobs/hr_lite/payroll_compute_job.rb +12 -0
  24. data/app/models/concerns/hr_lite/audited.rb +3 -1
  25. data/app/models/concerns/hr_lite/encrypted_money.rb +8 -5
  26. data/app/models/hr_lite/attendance_record.rb +14 -0
  27. data/app/models/hr_lite/comp_off_request.rb +2 -0
  28. data/app/models/hr_lite/document.rb +1 -1
  29. data/app/models/hr_lite/expense.rb +3 -0
  30. data/app/models/hr_lite/hr_request.rb +2 -0
  31. data/app/models/hr_lite/kudo.rb +2 -2
  32. data/app/models/hr_lite/leave_request.rb +25 -6
  33. data/app/models/hr_lite/payroll_line_item.rb +8 -0
  34. data/app/models/hr_lite/payroll_run.rb +15 -6
  35. data/app/models/hr_lite/regularization_request.rb +2 -2
  36. data/app/models/hr_lite/resignation.rb +5 -1
  37. data/app/models/hr_lite/role.rb +5 -0
  38. data/app/models/hr_lite/salary_slip.rb +1 -1
  39. data/app/models/hr_lite/tax_declaration.rb +5 -0
  40. data/app/services/hr_lite/attendance_puncher.rb +4 -0
  41. data/app/services/hr_lite/overview_query.rb +0 -12
  42. data/app/services/hr_lite/work_status.rb +5 -2
  43. data/app/views/hr_lite/admin/assets/index.html.erb +9 -2
  44. data/app/views/hr_lite/admin/attendances/show.html.erb +3 -1
  45. data/app/views/hr_lite/admin/attendances/summary.html.erb +5 -2
  46. data/app/views/hr_lite/admin/audit_logs/index.html.erb +6 -4
  47. data/app/views/hr_lite/admin/ca_reviews/index.html.erb +3 -3
  48. data/app/views/hr_lite/admin/checklists/index.html.erb +3 -3
  49. data/app/views/hr_lite/admin/comp_off_requests/index.html.erb +1 -2
  50. data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +5 -5
  51. data/app/views/hr_lite/admin/designation_changes/new.html.erb +4 -3
  52. data/app/views/hr_lite/admin/documents/index.html.erb +8 -3
  53. data/app/views/hr_lite/admin/documents/show.html.erb +1 -0
  54. data/app/views/hr_lite/admin/employees/_form.html.erb +12 -5
  55. data/app/views/hr_lite/admin/employees/show.html.erb +38 -32
  56. data/app/views/hr_lite/admin/expenses/index.html.erb +3 -2
  57. data/app/views/hr_lite/admin/holidays/index.html.erb +5 -4
  58. data/app/views/hr_lite/admin/hr_requests/index.html.erb +1 -1
  59. data/app/views/hr_lite/admin/hr_requests/show.html.erb +4 -3
  60. data/app/views/hr_lite/admin/leave_balances/index.html.erb +6 -4
  61. data/app/views/hr_lite/admin/leave_requests/index.html.erb +1 -2
  62. data/app/views/hr_lite/admin/leave_requests/show.html.erb +5 -3
  63. data/app/views/hr_lite/admin/leave_types/edit.html.erb +4 -4
  64. data/app/views/hr_lite/admin/leave_types/index.html.erb +3 -3
  65. data/app/views/hr_lite/admin/loans/index.html.erb +1 -1
  66. data/app/views/hr_lite/admin/loans/show.html.erb +8 -12
  67. data/app/views/hr_lite/admin/office_locations/edit.html.erb +4 -4
  68. data/app/views/hr_lite/admin/payroll_runs/index.html.erb +2 -2
  69. data/app/views/hr_lite/admin/payroll_runs/show.html.erb +10 -7
  70. data/app/views/hr_lite/admin/policies/index.html.erb +1 -1
  71. data/app/views/hr_lite/admin/policies/new.html.erb +2 -2
  72. data/app/views/hr_lite/admin/regularization_requests/index.html.erb +3 -4
  73. data/app/views/hr_lite/admin/regularization_requests/show.html.erb +4 -5
  74. data/app/views/hr_lite/admin/reports/index.html.erb +3 -6
  75. data/app/views/hr_lite/admin/reports/show.html.erb +16 -2
  76. data/app/views/hr_lite/admin/roles/_form.html.erb +6 -5
  77. data/app/views/hr_lite/admin/salary_slips/show.html.erb +3 -2
  78. data/app/views/hr_lite/admin/salary_structures/_form.html.erb +1 -0
  79. data/app/views/hr_lite/admin/settings/edit.html.erb +5 -1
  80. data/app/views/hr_lite/admin/statutory_rate_cards/_form.html.erb +3 -3
  81. data/app/views/hr_lite/admin/statutory_rate_cards/index.html.erb +3 -1
  82. data/app/views/hr_lite/admin/tax_declarations/index.html.erb +1 -1
  83. data/app/views/hr_lite/admin/tax_declarations/show.html.erb +2 -0
  84. data/app/views/hr_lite/admin/wfh_requests/index.html.erb +17 -16
  85. data/app/views/hr_lite/admin/wfh_requests/show.html.erb +2 -4
  86. data/app/views/hr_lite/admin/work_locations/index.html.erb +1 -1
  87. data/app/views/hr_lite/admin/work_modes/index.html.erb +1 -1
  88. data/app/views/hr_lite/approvals/index.html.erb +1 -1
  89. data/app/views/hr_lite/attendance/_month_grid.html.erb +9 -9
  90. data/app/views/hr_lite/attendance/_penalties.html.erb +2 -2
  91. data/app/views/hr_lite/attendance/_punch_card.html.erb +2 -4
  92. data/app/views/hr_lite/attendance/_slot.html.erb +9 -6
  93. data/app/views/hr_lite/attendance/_trail.html.erb +1 -1
  94. data/app/views/hr_lite/attendance/_work_mode.html.erb +1 -1
  95. data/app/views/hr_lite/attendance/show.html.erb +4 -4
  96. data/app/views/hr_lite/calendar/show.html.erb +2 -2
  97. data/app/views/hr_lite/comp_off_requests/index.html.erb +1 -1
  98. data/app/views/hr_lite/comp_off_requests/new.html.erb +1 -1
  99. data/app/views/hr_lite/documents/_upload_form.html.erb +1 -1
  100. data/app/views/hr_lite/employee_profiles/show.html.erb +4 -2
  101. data/app/views/hr_lite/home/index.html.erb +2 -2
  102. data/app/views/hr_lite/id_cards/show.html.erb +5 -1
  103. data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +2 -2
  104. data/app/views/hr_lite/leave_requests/new.html.erb +17 -8
  105. data/app/views/hr_lite/leave_requests/show.html.erb +2 -0
  106. data/app/views/hr_lite/org/show.html.erb +1 -1
  107. data/app/views/hr_lite/regularization_requests/index.html.erb +7 -7
  108. data/app/views/hr_lite/regularization_requests/new.html.erb +7 -5
  109. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +2 -2
  110. data/app/views/hr_lite/salary_slips/pdf.html.erb +1 -1
  111. data/app/views/hr_lite/tax/_computation.html.erb +1 -1
  112. data/app/views/hr_lite/tax_declarations/show.html.erb +4 -4
  113. data/app/views/hr_lite/team/show.html.erb +8 -7
  114. data/app/views/hr_lite/timelines/show.html.erb +1 -0
  115. data/app/views/hr_lite/wfh_requests/index.html.erb +3 -3
  116. data/app/views/hr_lite/work_time/_bars.html.erb +1 -1
  117. data/app/views/hr_lite/work_time/_donut.html.erb +1 -1
  118. data/app/views/hr_lite/work_time/_report.html.erb +14 -8
  119. data/app/views/layouts/hr_lite/application.html.erb +1 -0
  120. data/lib/hr_lite/notifications.rb +5 -2
  121. data/lib/hr_lite/version.rb +1 -1
  122. metadata +2 -1
@@ -10,7 +10,7 @@
10
10
  <% if @runs.any? %>
11
11
  <div class="hrl-table-wrap">
12
12
  <table class="hrl-table hrl-table--stack">
13
- <thead><tr><th>Period</th><th>Status</th><th>Slips</th><th>Net total</th><th></th></tr></thead>
13
+ <thead><tr><th>Period</th><th>Status</th><th>Slips</th><th>Net payout</th><th></th></tr></thead>
14
14
  <tbody>
15
15
  <% @runs.each do |run| %>
16
16
  <tr>
@@ -19,7 +19,7 @@
19
19
  <span class="hrl-badge <%= { "published" => "hrl-badge--ok", "finalized" => "hrl-badge--warn", "draft" => "hrl-badge--muted" }[run.status] %>"><%= run.status.humanize %></span>
20
20
  </td>
21
21
  <td data-label="Slips" class="hrl-num"><%= run.salary_slips.size %></td>
22
- <td data-label="Net total" class="hrl-num"><%= hrl_money(run.total_net) %></td>
22
+ <td data-label="Net payout" class="hrl-num"><%= hrl_money(run.total_net) %></td>
23
23
  <td data-label=""><a class="hrl-btn" href="<%= hr_lite.admin_payroll_run_path(run) %>">Open</a></td>
24
24
  </tr>
25
25
  <% end %>
@@ -2,7 +2,7 @@
2
2
  <div class="hrl-page__head">
3
3
  <h1 class="hrl-page__title">Payroll — <%= @run.label %></h1>
4
4
  <div class="hrl-page__actions">
5
- <a class="hrl-btn" href="<%= hr_lite.admin_payroll_line_items_path(month: @run.period_month.strftime("%Y-%m")) %>">One-off items</a>
5
+ <% if hr_can?("payroll.manage", scope: :all) %><a class="hrl-btn" href="<%= hr_lite.admin_payroll_line_items_path(month: @run.period_month.strftime("%Y-%m")) %>">One-off items</a><% end %>
6
6
  <a class="hrl-btn" href="<%= hr_lite.admin_payroll_runs_path %>">All runs</a>
7
7
  </div>
8
8
  </div>
@@ -29,7 +29,7 @@
29
29
  <% manager = hr_can?("payroll.manage", scope: :all) %>
30
30
  <% if manager && (@run.draft? || @run.review? || @run.processing?) %>
31
31
  <%= button_to @run.draft? ? "Compute" : "Recompute", hr_lite.compute_admin_payroll_run_path(@run),
32
- method: :post, class: "hrl-btn",
32
+ method: :post, class: @run.draft? ? "hrl-btn hrl-btn--primary" : "hrl-btn",
33
33
  form: { data: { turbo_confirm: ("Recompute #{@run.label}? This clears #{hr_display_name(@run.first_approved_by)}'s approval." if @run.first_approved_by) } } %>
34
34
  <% end %>
35
35
  <% if @run.review? && hr_can?("payroll.approve", scope: :all) && @run.first_approved_by_id != hr_current_user.id %>
@@ -56,6 +56,9 @@
56
56
  <p class="hrl-small hrl-muted hrl-mt">
57
57
  <% if @run.review? %>
58
58
  <%= @run.first_approved_by ? "Approved by #{hr_display_name(@run.first_approved_by)} on #{@run.first_approved_at.strftime('%-d %b')} — one more approver (HR or Super Admin) publishes it." : "Needs two approvals (HR and Super Admin). If nobody approves by the 3rd, a run with no blocking warnings is approved automatically." %>
59
+ <% elsif @run.processing? %>
60
+ <% content_for(:head) { tag.meta("http-equiv": "refresh", content: "3") } %>
61
+ Computing… this page refreshes until the slips are ready.
59
62
  <% elsif @run.auto_approved_at %>
60
63
  Approved automatically on <%= @run.auto_approved_at.strftime("%-d %b") %> — nobody had approved it by the 3rd.
61
64
  <% end %>
@@ -66,20 +69,20 @@
66
69
  <% if @slips.any? %>
67
70
  <div class="hrl-table-wrap">
68
71
  <table class="hrl-table hrl-table--stack">
69
- <thead><tr><th>Employee</th><th>Days</th><th>LOP</th><th>Gross</th><th>Deductions</th><th>Net</th><th></th></tr></thead>
72
+ <thead><tr><th>Employee</th><th>Paid days</th><th><abbr title="Loss of pay: unpaid days">LOP</abbr></th><th>Gross</th><th>Deductions</th><th>Net</th><th></th></tr></thead>
70
73
  <tbody>
71
74
  <% @slips.each do |slip| %>
72
75
  <tr>
73
76
  <td data-label="Employee"><%= hr_display_name(slip.user) %></td>
74
- <td data-label="Days" class="hrl-num"><%= slip.payable_days %></td>
75
- <td data-label="LOP" class="hrl-num">
76
- <%= slip.effective_lop_days %>
77
+ <td data-label="Paid days" class="hrl-num"><%= hrl_days(slip.payable_days) %></td>
78
+ <td data-label="Unpaid (LOP)" class="hrl-num">
79
+ <%= hrl_days(slip.effective_lop_days) %>
77
80
  <% if slip.lop_override %><span class="hrl-badge hrl-badge--warn">override</span><% end %>
78
81
  </td>
79
82
  <td data-label="Gross" class="hrl-num"><%= hrl_money(slip.gross_earnings) %></td>
80
83
  <td data-label="Deductions" class="hrl-num"><%= hrl_money(slip.total_deductions) %></td>
81
84
  <td data-label="Net" class="hrl-num"><strong><%= hrl_money(slip.net_pay) %></strong></td>
82
- <td data-label=""><a class="hrl-small" href="<%= hr_lite.admin_salary_slip_path(slip) %>">Detail</a></td>
85
+ <td data-label=""><a class="hrl-btn" href="<%= hr_lite.admin_salary_slip_path(slip) %>">Detail</a></td>
83
86
  </tr>
84
87
  <% end %>
85
88
  </tbody>
@@ -17,7 +17,7 @@
17
17
  <td data-label="Title"><a class="hrl-card__link" href="<%= hr_lite.policy_path(policy) %>"><%= policy.title %></a> <span class="hrl-badge hrl-badge--muted">v<%= policy.version %></span></td>
18
18
  <td data-label="Kind"><%= policy.announcement? ? "Announcement" : "Policy" %></td>
19
19
  <td data-label="In force"><%= policy.effective_from.strftime("%d %b %Y") %></td>
20
- <td data-label="Still to acknowledge"><%= policy.announcement? ? "—" : policy.outstanding_for.count %></td>
20
+ <td data-label="Still to acknowledge"><% if policy.announcement? %>—<% else %><% waiting = policy.outstanding_for.to_a %><details><summary class="hrl-btn"><%= pluralize(waiting.size, "person") %></summary><%= waiting.map { |u| hr_display_name(u) }.to_sentence %></details><% end %></td>
21
21
  </tr>
22
22
  <% end %>
23
23
  </tbody>
@@ -8,12 +8,12 @@
8
8
  <%= form_with model: @policy, url: hr_lite.admin_policies_path, method: :post, local: true do |f| %>
9
9
  <%= render "hr_lite/shared/form_errors", record: @policy %>
10
10
  <div class="hrl-field"><%= f.label :title %><%= f.text_field :title, required: true %></div>
11
- <div class="hrl-field"><%= f.label :body, "Text" %><%= f.text_area :body, rows: 10, required: true %></div>
12
- <div class="hrl-field"><%= f.label :effective_from, "In force from" %><%= f.date_field :effective_from %></div>
13
11
  <div class="hrl-field hrl-field--check">
14
12
  <%= f.check_box :acknowledgement_required %>
15
13
  <%= f.label :acknowledgement_required, "Policy — everyone must acknowledge it (untick for an announcement)" %>
16
14
  </div>
15
+ <div class="hrl-field"><%= f.label :body, "Text" %><%= f.text_area :body, rows: 10, required: true %></div>
16
+ <div class="hrl-field"><%= f.label :effective_from, "In force from" %><%= f.date_field :effective_from %></div>
17
17
  <div class="hrl-form-actions">
18
18
  <%= f.submit "Publish", class: "hrl-btn hrl-btn--primary",
19
19
  data: { turbo_confirm: "Publish and notify #{pluralize(HrLite.active_employees.size, 'person')} by email?" } %>
@@ -1,13 +1,12 @@
1
- <% content_for(:page_title) { "Regularization tickets" } %>
1
+ <% content_for(:page_title) { "Day fixes" } %>
2
2
  <div class="hrl-page__head">
3
- <h1 class="hrl-page__title">Regularization tickets</h1>
3
+ <h1 class="hrl-page__title">Day fixes</h1>
4
4
  </div>
5
5
 
6
6
  <div class="hrl-row hrl-row--mb">
7
7
  <span class="hrl-small hrl-muted">Status</span>
8
8
  <% HrLite::RegularizationRequest::STATUSES.each do |status| %>
9
- <a class="hrl-btn <%= 'hrl-btn--primary' if @status == status %>"
10
- href="<%= hr_lite.admin_regularization_requests_path(status: status) %>"><%= status.humanize %></a>
9
+ <%= link_to status.humanize, hr_lite.admin_regularization_requests_path(status: status), class: "hrl-btn", aria: { current: ("true" if @status == status) } %>
11
10
  <% end %>
12
11
  </div>
13
12
 
@@ -1,10 +1,7 @@
1
1
  <% content_for(:page_title) { "Ticket — #{hr_display_name(@request.user)}" } %>
2
+ <a class="hrl-backlink" href="<%= hr_lite.admin_regularization_requests_path %>"><span aria-hidden="true">‹</span> Approvals</a>
2
3
  <div class="hrl-page__head">
3
4
  <h1 class="hrl-page__title">Regularization — <%= hr_display_name(@request.user) %></h1>
4
- <div class="hrl-page__actions">
5
- <a class="hrl-btn" href="<%= hr_lite.admin_regularization_requests_path %>">All tickets</a>
6
- <a class="hrl-btn" href="<%= hr_lite.admin_attendance_path(@request.user_id, month: @request.date.strftime("%Y-%m")) %>">Their month</a>
7
- </div>
8
5
  </div>
9
6
 
10
7
  <section class="hrl-card">
@@ -28,6 +25,7 @@
28
25
  <% if @request.decision_note.present? %> · <%= @request.decision_note %><% end %></dd>
29
26
  <% end %>
30
27
  </dl>
28
+ <a class="hrl-card__link" href="<%= hr_lite.admin_attendance_path(@request.user_id, month: @request.date.strftime("%Y-%m")) %>">Their month</a>
31
29
  </section>
32
30
 
33
31
  <% if @request.pending? %>
@@ -38,7 +36,8 @@
38
36
  <%# Approve and reject are two separate forms on purpose. Rails binds the
39
37
  CSRF token to the form's OWN action, so one form retargeted with
40
38
  `formaction:` can never pass verification on the second endpoint. %>
41
- <%= form_with url: hr_lite.approve_admin_regularization_request_path(@request), method: :post, local: true do |f| %>
39
+ <%= form_with url: hr_lite.approve_admin_regularization_request_path(@request), method: :post, local: true,
40
+ html: { data: { turbo_confirm: "Write the proposed times onto #{hr_display_name(@request.user)}'s attendance for #{@request.date.strftime('%d %b %Y')}?" } } do |f| %>
42
41
  <div class="hrl-field">
43
42
  <%= f.label :decision_note, "Note (optional)", for: "approve_note" %>
44
43
  <%= f.text_area :decision_note, id: "approve_note" %>
@@ -9,7 +9,7 @@
9
9
  <%= form_with url: hr_lite.admin_reports_path, method: :get, local: true do %>
10
10
  <div class="hrl-field">
11
11
  <%= label_tag :month, "Month" %>
12
- <%= text_field_tag :month, @month.strftime("%Y-%m"), placeholder: "YYYY-MM" %>
12
+ <%= month_field_tag :month, @month.strftime("%Y-%m"), max: Date.current.strftime("%Y-%m"), class: "hrl-input" %>
13
13
  </div>
14
14
  <div class="hrl-form-actions">
15
15
  <%= submit_tag "Change month", class: "hrl-btn" %>
@@ -26,11 +26,8 @@
26
26
  <tr>
27
27
  <td data-label="Report"><%= label %></td>
28
28
  <td data-label="">
29
- <a class="hrl-small"
30
- href="<%= hr_lite.admin_report_path(name, month: @month.strftime("%Y-%m")) %>">Open</a>
31
- &nbsp;
32
- <a class="hrl-small"
33
- href="<%= hr_lite.admin_report_path(name, month: @month.strftime("%Y-%m"), format: :csv) %>">CSV</a>
29
+ <a class="hrl-btn" href="<%= hr_lite.admin_report_path(name, month: @month.strftime("%Y-%m")) %>">Open</a>
30
+ <a class="hrl-btn" href="<%= hr_lite.admin_report_path(name, month: @month.strftime("%Y-%m"), format: :csv) %>">CSV</a>
34
31
  </td>
35
32
  </tr>
36
33
  <% end %>
@@ -1,7 +1,21 @@
1
1
  <% content_for(:page_title) { @reports_title = HrLite::Admin::ReportsController::REPORTS[@name] } %>
2
+ <% content_for(:back) { "Reports" } %>
3
+ <%# The screen reads like the rest of the app; the CSV keeps raw values for spreadsheets. %>
4
+ <% heading = ->(h) { h == "LOP" ? "Unpaid days (LOP)" : h }
5
+ shown = lambda do |header, value|
6
+ if value.blank? then "—"
7
+ elsif header == "Amount" then hrl_money(value)
8
+ elsif header.in?([ "Joined", "Left", "Spent on" ]) then Date.parse(value.to_s).strftime("%d %b %Y")
9
+ elsif header == "Status" then value.to_s.humanize
10
+ elsif value.is_a?(Float) then hrl_days(value)
11
+ else value
12
+ end
13
+ end %>
2
14
  <div class="hrl-page__head">
3
15
  <h1 class="hrl-page__title"><%= HrLite::Admin::ReportsController::REPORTS[@name] %></h1>
4
16
  <div class="hrl-page__actions">
17
+ <a class="hrl-btn" href="<%= hr_lite.admin_report_path(@name, month: @month.prev_month.strftime("%Y-%m")) %>">‹ <%= @month.prev_month.strftime("%b %Y") %></a>
18
+ <% if @month.next_month <= Date.current %><a class="hrl-btn" href="<%= hr_lite.admin_report_path(@name, month: @month.next_month.strftime("%Y-%m")) %>"><%= @month.next_month.strftime("%b %Y") %> ›</a><% end %>
5
19
  <a class="hrl-btn"
6
20
  href="<%= hr_lite.admin_report_path(@name, month: @month.strftime("%Y-%m"), format: :csv) %>">Download CSV</a>
7
21
  </div>
@@ -15,13 +29,13 @@
15
29
  <div class="hrl-table-wrap">
16
30
  <table class="hrl-table hrl-table--stack">
17
31
  <thead>
18
- <tr><% @rows.first.keys.each do |header| %><th><%= header %></th><% end %></tr>
32
+ <tr><% @rows.first.keys.each do |header| %><th><%= heading.(header) %></th><% end %></tr>
19
33
  </thead>
20
34
  <tbody>
21
35
  <% @rows.each do |row| %>
22
36
  <tr>
23
37
  <% row.each do |header, value| %>
24
- <td data-label="<%= header %>"><%= value %></td>
38
+ <td data-label="<%= heading.(header) %>"><%= shown.(header, value) %></td>
25
39
  <% end %>
26
40
  </tr>
27
41
  <% end %>
@@ -7,8 +7,8 @@
7
7
  <% if role.system? %>
8
8
  <%= f.text_field :name, disabled: true %>
9
9
  <span class="hrl-small hrl-muted">
10
- Built-in roles keep their name — the upgrade path and the seed both
11
- identify them by it. The permissions below are yours to change.
10
+ Built-in roles keep their name, because the system finds them by it.
11
+ The permissions below are yours to change.
12
12
  </span>
13
13
  <% else %>
14
14
  <%= f.text_field :name %>
@@ -20,8 +20,9 @@
20
20
  </div>
21
21
 
22
22
  <% HrLite::Permissions.grouped.each do |group, keys| %>
23
- <fieldset>
24
- <legend><%= group %></legend>
23
+ <% granted = keys.count { |k| (role.grant_map[k] || "none") != "none" } %>
24
+ <details class="hrl-card" <%= "open" if granted.positive? %>>
25
+ <summary class="hrl-btn hrl-btn--block"><%= group %> · <%= granted %> of <%= keys.size %> granted</summary>
25
26
  <% keys.each do |key| %>
26
27
  <% held = role.grant_map[key] || "none" %>
27
28
  <div class="hrl-field">
@@ -36,7 +37,7 @@
36
37
  id: "grants_#{key.tr('.', '_')}" %>
37
38
  </div>
38
39
  <% end %>
39
- </fieldset>
40
+ </details>
40
41
  <% end %>
41
42
 
42
43
  <div class="hrl-form-actions">
@@ -11,8 +11,9 @@
11
11
  <section class="hrl-card">
12
12
  <h2 class="hrl-card__title">Tax working</h2>
13
13
  <dl class="hrl-deflist">
14
- <% @slip.tax_details_hash.each do |key, value| %>
15
- <dt><%= key.humanize %></dt><dd><%= value %></dd>
14
+ <% @slip.tax_details_hash.except("high_income_review").each do |key, value| %>
15
+ <dt><%= key == "fy_tds_already_deducted" ? "TDS already deducted this year" : key.humanize %></dt>
16
+ <dd><%= key.in?(%w[regime months_remaining]) ? value.to_s.humanize : hrl_money(value) %></dd>
16
17
  <% end %>
17
18
  </dl>
18
19
  <% if @slip.tax_details_hash["high_income_review"] == "true" %>
@@ -5,6 +5,7 @@
5
5
  <div class="hrl-field">
6
6
  <%= f.label :annual_ctc, "Annual CTC (₹ a year)" %>
7
7
  <%= f.number_field :annual_ctc, step: 1, min: 1, required: true, value: (structure.annual_ctc || breakup&.dig(:annual_ctc))&.round %>
8
+ <% if structure.annual_ctc %><p class="hrl-hint">= <%= hrl_money(structure.annual_ctc) %> a year</p><% end %>
8
9
  <p class="hrl-hint">The only amount you type. Saving splits it: Basic 50%, HRA 50% of Basic in a metro (40% elsewhere), employer PF, ESI and gratuity (4.81% of Basic) inside the CTC, the rest as special allowance.</p>
9
10
  </div>
10
11
  <div class="hrl-field">
@@ -6,11 +6,13 @@
6
6
  <section class="hrl-card">
7
7
  <%= form_with model: @setting, url: hr_lite.admin_setting_path, method: :patch, local: true do |f| %>
8
8
  <%= render "hr_lite/shared/form_errors", record: @setting %>
9
+ <h2 class="hrl-card__title">Working week</h2>
9
10
  <div class="hrl-field">
10
11
  <%= f.label :weekend_policy, "Weekly off policy" %>
11
- <%= f.select :weekend_policy, HrLite::Setting::WEEKEND_POLICIES.map { |p| [ p.humanize, p ] } %>
12
+ <%= f.select :weekend_policy, { "Sunday only" => "sun_only", "Saturday and Sunday" => "sat_sun", "Sunday, 2nd and 4th Saturday" => "second_fourth_sat_sun" }.select { |_, v| v.in?(HrLite::Setting::WEEKEND_POLICIES) } %>
12
13
  <p class="hrl-hint">Drives working-day math everywhere: leave counting, attendance, payroll.</p>
13
14
  </div>
15
+ <h2 class="hrl-card__title hrl-mt">Breaks</h2>
14
16
  <div class="hrl-field">
15
17
  <%= f.label :lunch_break_minutes, "Lunch break limit (minutes)" %>
16
18
  <%= f.number_field :lunch_break_minutes, min: 5, max: 120 %>
@@ -20,6 +22,7 @@
20
22
  <%= f.label :short_break_minutes, "Short break limit (minutes)" %>
21
23
  <%= f.number_field :short_break_minutes, min: 1, max: 60 %>
22
24
  </div>
25
+ <h2 class="hrl-card__title hrl-mt">Working day</h2>
23
26
  <div class="hrl-field">
24
27
  <%= f.label :day_minutes, "Working day (minutes, break included)" %>
25
28
  <%= f.number_field :day_minutes, min: 240, max: 720, step: 15 %>
@@ -39,6 +42,7 @@
39
42
  <%= f.time_field :recheck_in_until %>
40
43
  <p class="hrl-hint">After checking out, a person can check in again once a day inside these hours.</p>
41
44
  </div>
45
+ <h2 class="hrl-card__title hrl-mt">Penalties and codes</h2>
42
46
  <div class="hrl-field">
43
47
  <%= f.label :penalty_leave_type_id, "Penalties come off" %>
44
48
  <%= f.collection_select :penalty_leave_type_id, HrLite::LeaveType.active, :id, :name, { include_blank: "No leave (penalties stay as time)" }, data: { hrl_filter: "Type a leave type to filter" } %>
@@ -18,8 +18,8 @@
18
18
 
19
19
  <fieldset>
20
20
  <legend>Provident fund</legend>
21
- <% { employee_rate: "Employee rate (0.12 = 12%)", employer_rate: "Employer rate",
22
- eps_rate: "EPS rate", wage_ceiling: "PF wage ceiling (₹)",
21
+ <% { employee_rate: "Employee rate (0.12 = 12%)", employer_rate: "Employer rate (0.12 = 12%)",
22
+ eps_rate: "EPS rate (0.0833 = 8.33%)", wage_ceiling: "PF wage ceiling (₹)",
23
23
  eps_wage_ceiling: "EPS wage ceiling (₹)", edli_rate: "EDLI rate",
24
24
  edli_ceiling: "EDLI ceiling (₹)", admin_rate: "PF admin rate" }.each do |key, label| %>
25
25
  <div class="hrl-field">
@@ -32,7 +32,7 @@
32
32
 
33
33
  <fieldset>
34
34
  <legend>ESI</legend>
35
- <% { employee_rate: "Employee rate", employer_rate: "Employer rate",
35
+ <% { employee_rate: "Employee rate (0.0075 = 0.75%)", employer_rate: "Employer rate (0.0325 = 3.25%)",
36
36
  gross_ceiling: "Gross ceiling (₹)" }.each do |key, label| %>
37
37
  <div class="hrl-field">
38
38
  <%= label_tag "statutory_rate_card_esi_#{key}", label %>
@@ -16,10 +16,12 @@
16
16
  <table class="hrl-table hrl-table--stack">
17
17
  <thead><tr><th>Year</th><th>PF ceiling</th><th>ESI ceiling</th><th>Verified</th><th></th></tr></thead>
18
18
  <tbody>
19
+ <% live = HrLite::StatutoryRateCardRecord.effective_for(Date.current.beginning_of_month) %>
19
20
  <% @cards.each do |card| %>
20
21
  <tr>
21
22
  <td data-label="Year">
22
23
  <strong>FY <%= card.financial_year %></strong>
24
+ <% if card == live %><span class="hrl-badge hrl-badge--ok">In force</span><% end %>
23
25
  <div class="hrl-small hrl-muted">from <%= card.effective_from.strftime("%d %b %Y") %></div>
24
26
  </td>
25
27
  <td data-label="PF ceiling" class="hrl-num"><%= HrLite::Money.format(card.to_card[:pf][:wage_ceiling]) %></td>
@@ -33,7 +35,7 @@
33
35
  <% end %>
34
36
  </td>
35
37
  <td data-label="">
36
- <a class="hrl-small" href="<%= hr_lite.edit_admin_statutory_rate_card_path(card) %>">Edit</a>
38
+ <a class="hrl-btn" href="<%= hr_lite.edit_admin_statutory_rate_card_path(card) %>">Edit</a>
37
39
  </td>
38
40
  </tr>
39
41
  <% end %>
@@ -26,7 +26,7 @@
26
26
  <td data-label="Regime"><%= declaration.regime.humanize %></td>
27
27
  <td data-label="Claimed" class="hrl-num"><%= hrl_money(declaration.declared_total) %></td>
28
28
  <td data-label="">
29
- <a class="hrl-small" href="<%= hr_lite.admin_tax_declaration_path(declaration) %>">Check</a>
29
+ <a class="hrl-btn" href="<%= hr_lite.admin_tax_declaration_path(declaration) %>">Check</a>
30
30
  </td>
31
31
  </tr>
32
32
  <% end %>
@@ -53,6 +53,7 @@
53
53
  </section>
54
54
  <% end %>
55
55
 
56
+ <% if @declaration.submitted? %>
56
57
  <section class="hrl-card">
57
58
  <h2 class="hrl-card__title">Decide</h2>
58
59
  <div class="hrl-row">
@@ -71,3 +72,4 @@
71
72
  <%= submit_tag "Send back", class: "hrl-btn" %>
72
73
  <% end %>
73
74
  </section>
75
+ <% end %>
@@ -3,7 +3,22 @@
3
3
  <h1 class="hrl-page__title">Work from home</h1>
4
4
  </div>
5
5
 
6
- <section class="hrl-card">
6
+ <% if @grantable.any? %>
7
+ <section class="hrl-card">
8
+ <h2 class="hrl-card__title">Give WFH</h2>
9
+ <p class="hrl-small hrl-muted">Approved straight away, up to <%= HrLite::WfhRequest::HR_MAX_DAYS %> days from today or later.</p>
10
+ <%= form_with url: hr_lite.admin_wfh_requests_path, method: :post, local: true do %>
11
+ <div class="hrl-field"><label for="grant-user">Employee</label><%= select_tag :user_id, options_for_select(@grantable.map { |u| [ hr_display_name(u), u.id ] }), include_blank: "Choose one", required: true, id: "grant-user", data: { hrl_filter: ("Type a name to filter" if @grantable.size > 5) } %></div>
12
+ <div class="hrl-field"><label for="grant-from">From</label><%= date_field_tag :start_date, Date.current, min: Date.current, required: true, id: "grant-from" %></div>
13
+ <div class="hrl-field"><label for="grant-to">To</label><%= date_field_tag :end_date, Date.current, min: Date.current, required: true, id: "grant-to" %></div>
14
+ <div class="hrl-field"><label for="grant-reason">Reason</label><%= text_area_tag :reason, nil, rows: 2, required: true, id: "grant-reason" %></div>
15
+ <div class="hrl-form-actions"><button type="submit" class="hrl-btn hrl-btn--primary">Give WFH</button></div>
16
+ <% end %>
17
+ </section>
18
+ <% end %>
19
+
20
+ <details class="hrl-card"<%= " open" if request.query_string.present? %>>
21
+ <summary class="hrl-btn hrl-btn--block">Filter</summary>
7
22
  <%= form_with url: hr_lite.admin_wfh_requests_path, method: :get, local: true do %>
8
23
  <div class="hrl-field"><label for="wfh-from">From</label><%= date_field_tag :from, @from, id: "wfh-from" %></div>
9
24
  <div class="hrl-field"><label for="wfh-to">To</label><%= date_field_tag :to, @to, id: "wfh-to" %></div>
@@ -19,7 +34,7 @@
19
34
  <a class="hrl-btn" href="<%= hr_lite.admin_wfh_requests_path %>">Reset</a>
20
35
  </div>
21
36
  <% end %>
22
- </section>
37
+ </details>
23
38
 
24
39
  <div class="hrl-kpis">
25
40
  <div class="hrl-kpi"><div class="hrl-kpi__value"><%= @kpis[:requested] %></div><div class="hrl-kpi__label">Employees requested</div></div>
@@ -48,20 +63,6 @@
48
63
  <% end %>
49
64
  </section>
50
65
 
51
- <% if @grantable.any? %>
52
- <section class="hrl-card">
53
- <h2 class="hrl-card__title">Give WFH</h2>
54
- <p class="hrl-small hrl-muted">Approved straight away, up to <%= HrLite::WfhRequest::HR_MAX_DAYS %> days from today or later.</p>
55
- <%= form_with url: hr_lite.admin_wfh_requests_path, method: :post, local: true do %>
56
- <div class="hrl-field"><label for="grant-user">Employee</label><%= select_tag :user_id, options_for_select(@grantable.map { |u| [ hr_display_name(u), u.id ] }), include_blank: "Choose one", required: true, id: "grant-user", data: { hrl_filter: ("Type a name to filter" if @grantable.size > 5) } %></div>
57
- <div class="hrl-field"><label for="grant-from">From</label><%= date_field_tag :start_date, Date.current, min: Date.current, required: true, id: "grant-from" %></div>
58
- <div class="hrl-field"><label for="grant-to">To</label><%= date_field_tag :end_date, Date.current, min: Date.current, required: true, id: "grant-to" %></div>
59
- <div class="hrl-field"><label for="grant-reason">Reason</label><%= text_area_tag :reason, nil, rows: 2, required: true, id: "grant-reason" %></div>
60
- <div class="hrl-form-actions"><button type="submit" class="hrl-btn hrl-btn--primary">Give WFH</button></div>
61
- <% end %>
62
- </section>
63
- <% end %>
64
-
65
66
  <section class="hrl-card">
66
67
  <h2 class="hrl-card__title">Schedules <%= @from.strftime("%-d %b") %> – <%= @to.strftime("%-d %b") %></h2>
67
68
  <% if @requests.any? %>
@@ -1,10 +1,7 @@
1
1
  <% content_for(:page_title) { "WFH — #{hr_display_name(@request.user)}" } %>
2
+ <a class="hrl-backlink" href="<%= hr_lite.admin_wfh_requests_path %>"><span aria-hidden="true">‹</span> Approvals</a>
2
3
  <div class="hrl-page__head">
3
4
  <h1 class="hrl-page__title">WFH — <%= hr_display_name(@request.user) %></h1>
4
- <div class="hrl-page__actions">
5
- <a class="hrl-btn" href="<%= hr_lite.admin_wfh_requests_path %>">All WFH</a>
6
- <a class="hrl-btn" href="<%= hr_lite.admin_attendance_path(@request.user_id, month: @request.start_date.strftime("%Y-%m")) %>">Their month</a>
7
- </div>
8
5
  </div>
9
6
 
10
7
  <section class="hrl-card">
@@ -19,6 +16,7 @@
19
16
  <% if @request.decision_note.present? %> · <%= @request.decision_note %><% end %></dd>
20
17
  <% end %>
21
18
  </dl>
19
+ <a class="hrl-card__link" href="<%= hr_lite.admin_attendance_path(@request.user_id, month: @request.start_date.strftime("%Y-%m")) %>">Their month</a>
22
20
  </section>
23
21
 
24
22
  <% if @request.pending? && @decidable %>
@@ -6,7 +6,7 @@
6
6
  <div class="hrl-row hrl-row--mb">
7
7
  <span class="hrl-small hrl-muted">Status</span>
8
8
  <% HrLite::WorkLocation::STATUSES.each do |status| %>
9
- <a class="hrl-btn <%= 'hrl-btn--primary' if @status == status %>" href="<%= hr_lite.admin_work_locations_path(status: status) %>"><%= status.humanize %></a>
9
+ <a class="hrl-btn" <%= "aria-current=true" if @status == status %> href="<%= hr_lite.admin_work_locations_path(status: status) %>"><%= status.humanize %></a>
10
10
  <% end %>
11
11
  </div>
12
12
 
@@ -90,7 +90,7 @@
90
90
  <details>
91
91
  <summary class="hrl-btn">Set their mode</summary>
92
92
  <%= form_with url: hr_lite.admin_work_mode_override_path(@person.id), method: :post, local: true do %>
93
- <div class="hrl-field"><label for="ov-mode">Mode</label><%= select_tag :mode, options_for_select(HrLite::WorkModeSwitch::SELECTABLE.map { |m| [ HrLite::WorkModeEvent.label(m), m ] }), id: "ov-mode" %></div>
93
+ <div class="hrl-field"><label for="ov-mode">Mode</label><%= select_tag :mode, options_for_select(HrLite::WorkModeSwitch::SELECTABLE.map { |m| [ HrLite::WorkModeEvent.label(m), m ] }), id: "ov-mode", data: { hrl_filter: "Type a mode" } %></div>
94
94
  <div class="hrl-field"><label for="ov-title">Title / purpose (meeting, travel, client meeting)</label><input class="hrl-input" id="ov-title" name="title" maxlength="120"></div>
95
95
  <div class="hrl-field"><label for="ov-client">Client</label><input class="hrl-input" id="ov-client" name="client" maxlength="120"></div>
96
96
  <div class="hrl-field"><label for="ov-dest">Destination</label><input class="hrl-input" id="ov-dest" name="destination" maxlength="120"></div>
@@ -13,7 +13,7 @@
13
13
  </section>
14
14
  <% end %>
15
15
 
16
- <% rows = hrl_inbox_rows %>
16
+ <% rows = hrl_inbox_rows.reject { |_, record| @in_flow.include?([ record.class.polymorphic_name, record.id ]) } %>
17
17
  <section class="hrl-card">
18
18
  <% if @approvals.empty? && rows.empty? %>
19
19
  <p class="hrl-muted">Nothing is waiting on you.</p>
@@ -4,6 +4,15 @@
4
4
  link_builder = local_assigns[:link_builder]
5
5
  pending_dates = local_assigns[:pending_dates]
6
6
  states = HrLite::ApplicationHelper::DAY_STATES %>
7
+ <div class="hrl-legend hrl-small">
8
+ <% states.except(:upcoming).each_value do |code, word, mod| %>
9
+ <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--<%= mod %>"><%= code %> <%= word %></span>
10
+ <% end %>
11
+ <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--today">Today</span>
12
+ <span class="hrl-badge hrl-mgrid__key"><span class="hrl-mgrid__flag" aria-hidden="true"></span> Flagged</span>
13
+ <% if pending_dates %><span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--pending">Fix pending</span><% end %>
14
+ <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--muted">Upcoming</span>
15
+ </div>
7
16
  <div class="hrl-mgrid" role="group" aria-label="Attendance for <%= month.strftime('%B %Y') %>">
8
17
  <% %w[Mo Tu We Th Fr Sa Su].each do |d| %>
9
18
  <div class="hrl-mgrid__head"><%= d %></div>
@@ -25,12 +34,3 @@
25
34
  </div>
26
35
  <% end %>
27
36
  </div>
28
- <div class="hrl-legend hrl-small">
29
- <% states.except(:upcoming).each_value do |code, word, mod| %>
30
- <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--<%= mod %>"><%= code %> <%= word %></span>
31
- <% end %>
32
- <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--today">Today</span>
33
- <span class="hrl-badge hrl-mgrid__key"><span class="hrl-mgrid__flag" aria-hidden="true"></span> Flagged</span>
34
- <% if pending_dates %><span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--pending">Fix pending</span><% end %>
35
- <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--muted">Upcoming</span>
36
- </div>
@@ -3,8 +3,8 @@
3
3
  leave_type = HrLite::Setting.instance.penalty_leave_type %>
4
4
  <section class="hrl-card" id="penalties">
5
5
  <h2 class="hrl-card__title">Penalty hours</h2>
6
- <p><strong><%= hrl_duration(held * 60) %></strong> held · <%= hrl_days(HrLite::AttendancePenalty.days_for(held)) %> days<%= " of #{leave_type.name}" if leave_type %></p>
7
- <p class="hrl-hint">Penalties add up until HR removes them. Every 5 hours holds half a day of leave, which can't be applied for.</p>
6
+ <p><strong><%= hrl_duration(held * 60) %></strong> held · <%= pluralize(hrl_days(HrLite::AttendancePenalty.days_for(held)), "day") %><%= " of #{leave_type.name}" if leave_type %></p>
7
+ <p class="hrl-hint">Penalties add up until HR removes them.<% if leave_type %> Every 5 hours holds half a day of leave, which can't be applied for.<% end %></p>
8
8
  <% HrLite::PenaltyRemoval.where(user_id: user.id).includes(:removed_by).order(created_at: :desc).each do |removal| %>
9
9
  <div class="hrl-feed__item hrl-small"><div class="hrl-feed__body">Removed <%= hrl_duration(removal.minutes * 60) %> on <%= removal.created_at.strftime("%-d %b %Y") %> by <%= hr_display_name(removal.removed_by) %>: <%= removal.reason %></div></div>
10
10
  <% end %>
@@ -13,7 +13,7 @@
13
13
 
14
14
  <div class="hrl-row hrl-row--mb hrl-small">
15
15
  <% if record&.check_in_at && !record.check_out_at %>
16
- <span class="hrl-badge hrl-badge--ok">In since <%= record.check_in_at.strftime("%H:%M") %> · <%= hrl_duration(Time.current - record.check_in_at) %> so far</span>
16
+ <span class="hrl-badge hrl-badge--ok">In since <%= record.check_in_at.strftime("%H:%M") %> · <span data-hrl-since="<%= record.check_in_at.iso8601 %>"><%= hrl_duration(Time.current - record.check_in_at) %> so far</span></span>
17
17
  <% elsif record&.check_in_at %>
18
18
  <span class="hrl-badge hrl-badge--ok">In <%= record.check_in_at.strftime("%H:%M") %></span>
19
19
  <% end %>
@@ -142,9 +142,7 @@
142
142
 
143
143
  <% if local_assigns.key?(:attention) %>
144
144
  <div class="hrl-row">
145
- <a class="hrl-btn" href="<%= hr_lite.new_leave_request_path %>">Apply leave</a>
146
- <% if HrLite.config.office_geofence %><a class="hrl-btn" href="<%= hr_lite.wfh_requests_path %>">Request WFH</a><% end %>
147
- <a class="hrl-btn" href="<%= hr_lite.new_regularization_request_path %>">Fix a punch</a>
145
+ <a class="hrl-btn" href="<%= hr_lite.new_regularization_request_path %>">Fix a day</a>
148
146
  <a class="hrl-btn" href="<%= hr_lite.new_comp_off_request_path %>">Comp-off</a>
149
147
  <% if hr_lite_route?(:salary_slips_path) %><a class="hrl-btn" href="<%= hr_lite.salary_slips_path %>">Payslip</a><% end %>
150
148
  </div>
@@ -12,7 +12,7 @@
12
12
  <% elsif due %>
13
13
  <% left = due - Time.current %>
14
14
  <p class="hrl-small">Checked in <%= record.check_in_at.strftime("%H:%M") %> · checkout at <strong><%= due.strftime("%H:%M") %></strong> ·
15
- <%= left.positive? ? "#{hrl_duration(left)} left" : "you can check out" %></p>
15
+ <span data-hrl-countdown="<%= due.iso8601 %>" data-hrl-done="you can check out"><%= left.positive? ? "#{hrl_duration(left)} left" : "you can check out" %></span></p>
16
16
  <% end %>
17
17
  <% HrLite::AttendancePenalty.active.where(user_id: hr_current_user.id, date: Date.current).each do |penalty| %>
18
18
  <p class="hrl-small"><span class="hrl-badge hrl-badge--warn">+<%= penalty.minutes %> min</span> <%= penalty.label %></p>
@@ -23,11 +23,14 @@
23
23
  <label for="hrl-slot-start">Check-in slot</label>
24
24
  <%= select_tag :start_minute, options_for_select(HrLite::AttendanceSlot::START_MINUTES.map { |m| [ "#{HrLite::AttendanceSlot.label(m)} – out #{HrLite::AttendanceSlot.label(m + HrLite::EmployeeProfile.day_minutes_for(hr_current_user.id))}", m ] }, start), id: "hrl-slot-start" %>
25
25
  </div>
26
- <div class="hrl-field">
27
- <label for="hrl-slot-period">For</label>
28
- <%= select_tag :period, options_for_select(HrLite::AttendanceSlot::PERIODS.invert), id: "hrl-slot-period" %>
29
- </div>
30
- <button type="submit" class="hrl-btn hrl-btn--block">Save and lock slot</button>
26
+ <fieldset class="hrl-field">
27
+ <legend>Lock this slot for</legend>
28
+ <div class="hrl-mode-switch">
29
+ <% HrLite::AttendanceSlot::PERIODS.each do |period, word| %>
30
+ <button type="submit" name="period" value="<%= period %>" class="hrl-btn" data-turbo-confirm="Lock this slot for <%= word.downcase %>? After that only HR can change it."><%= word %></button>
31
+ <% end %>
32
+ </div>
33
+ </fieldset>
31
34
  <p class="hrl-small hrl-muted">Once saved, only HR can change it for that period.</p>
32
35
  <% end %>
33
36
  <% end %>
@@ -8,7 +8,7 @@
8
8
  <% if home_m %> · <%= number_with_delimiter(home_m) %> m from home<% end %>
9
9
  <% if accuracy %> · ±<%= accuracy %> m<% end %></div>
10
10
  <% end %>
11
- <% if (punches = record.punches).size > 2 %>
11
+ <% if (punches = local_assigns.fetch(:punches) { record.punches }).size > 2 %>
12
12
  <div class="hrl-punch-line">All punches: <%= punches.join(" · ") %></div>
13
13
  <% end %>
14
14
  <% if (route = record.route_url) %>
@@ -13,7 +13,7 @@
13
13
  </div>
14
14
  <%= render "hr_lite/attendance/custom_status", status: status %>
15
15
  <%= render "hr_lite/attendance/calendar_connection" %>
16
- <% if status.half_day_leave %><p class="hrl-small hrl-muted">Half-day leave today (<%= status.half_day_leave.half_day_part || "half day" %>).</p><% end %>
16
+ <% if status.half_day_leave %><p class="hrl-small hrl-muted">Half-day leave today (<%= HrLite::DAY_PARTS.rassoc(status.half_day_leave.half_day_part)&.first&.downcase || "half day" %>).</p><% end %>
17
17
 
18
18
  <% if working %>
19
19
  <% if status.overlay %>