hr_lite 0.54.0 → 0.56.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -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 +6 -2
  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 +20 -14
  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/benefit_enrolment.rb +3 -1
  28. data/app/models/hr_lite/comp_off_request.rb +2 -0
  29. data/app/models/hr_lite/document.rb +1 -1
  30. data/app/models/hr_lite/expense.rb +13 -5
  31. data/app/models/hr_lite/hr_request.rb +2 -0
  32. data/app/models/hr_lite/kudo.rb +2 -2
  33. data/app/models/hr_lite/leave_request.rb +25 -6
  34. data/app/models/hr_lite/payroll_line_item.rb +8 -0
  35. data/app/models/hr_lite/payroll_run.rb +18 -6
  36. data/app/models/hr_lite/regularization_request.rb +2 -2
  37. data/app/models/hr_lite/resignation.rb +5 -1
  38. data/app/models/hr_lite/role.rb +5 -0
  39. data/app/models/hr_lite/salary_slip.rb +1 -1
  40. data/app/models/hr_lite/tax_declaration.rb +5 -0
  41. data/app/services/hr_lite/attendance_puncher.rb +4 -0
  42. data/app/services/hr_lite/overview_query.rb +0 -12
  43. data/app/services/hr_lite/payroll_run_processor.rb +3 -0
  44. data/app/services/hr_lite/slip_builder.rb +17 -9
  45. data/app/services/hr_lite/work_status.rb +5 -2
  46. data/app/views/hr_lite/admin/assets/index.html.erb +9 -2
  47. data/app/views/hr_lite/admin/attendances/show.html.erb +3 -1
  48. data/app/views/hr_lite/admin/attendances/summary.html.erb +5 -2
  49. data/app/views/hr_lite/admin/audit_logs/index.html.erb +6 -4
  50. data/app/views/hr_lite/admin/ca_reviews/index.html.erb +3 -3
  51. data/app/views/hr_lite/admin/checklists/index.html.erb +3 -3
  52. data/app/views/hr_lite/admin/comp_off_requests/index.html.erb +1 -2
  53. data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +5 -5
  54. data/app/views/hr_lite/admin/designation_changes/new.html.erb +4 -3
  55. data/app/views/hr_lite/admin/documents/index.html.erb +8 -3
  56. data/app/views/hr_lite/admin/documents/show.html.erb +2 -1
  57. data/app/views/hr_lite/admin/employees/_form.html.erb +12 -5
  58. data/app/views/hr_lite/admin/employees/show.html.erb +38 -32
  59. data/app/views/hr_lite/admin/expenses/index.html.erb +4 -3
  60. data/app/views/hr_lite/admin/holidays/index.html.erb +5 -4
  61. data/app/views/hr_lite/admin/hr_requests/index.html.erb +1 -1
  62. data/app/views/hr_lite/admin/hr_requests/show.html.erb +4 -3
  63. data/app/views/hr_lite/admin/leave_balances/index.html.erb +6 -4
  64. data/app/views/hr_lite/admin/leave_requests/index.html.erb +1 -2
  65. data/app/views/hr_lite/admin/leave_requests/show.html.erb +5 -3
  66. data/app/views/hr_lite/admin/leave_types/edit.html.erb +4 -4
  67. data/app/views/hr_lite/admin/leave_types/index.html.erb +3 -3
  68. data/app/views/hr_lite/admin/loans/index.html.erb +2 -2
  69. data/app/views/hr_lite/admin/loans/show.html.erb +8 -12
  70. data/app/views/hr_lite/admin/office_locations/edit.html.erb +4 -4
  71. data/app/views/hr_lite/admin/payroll_runs/index.html.erb +2 -2
  72. data/app/views/hr_lite/admin/payroll_runs/show.html.erb +10 -7
  73. data/app/views/hr_lite/admin/policies/index.html.erb +1 -1
  74. data/app/views/hr_lite/admin/policies/new.html.erb +2 -2
  75. data/app/views/hr_lite/admin/regularization_requests/index.html.erb +3 -4
  76. data/app/views/hr_lite/admin/regularization_requests/show.html.erb +4 -5
  77. data/app/views/hr_lite/admin/reports/index.html.erb +3 -6
  78. data/app/views/hr_lite/admin/reports/show.html.erb +16 -2
  79. data/app/views/hr_lite/admin/roles/_form.html.erb +6 -5
  80. data/app/views/hr_lite/admin/salary_slips/show.html.erb +3 -2
  81. data/app/views/hr_lite/admin/salary_structures/_form.html.erb +1 -0
  82. data/app/views/hr_lite/admin/settings/edit.html.erb +5 -1
  83. data/app/views/hr_lite/admin/statutory_rate_cards/_form.html.erb +3 -3
  84. data/app/views/hr_lite/admin/statutory_rate_cards/index.html.erb +3 -1
  85. data/app/views/hr_lite/admin/tax_declarations/index.html.erb +1 -1
  86. data/app/views/hr_lite/admin/tax_declarations/show.html.erb +2 -0
  87. data/app/views/hr_lite/admin/wfh_requests/index.html.erb +17 -16
  88. data/app/views/hr_lite/admin/wfh_requests/show.html.erb +2 -4
  89. data/app/views/hr_lite/admin/work_locations/index.html.erb +1 -1
  90. data/app/views/hr_lite/admin/work_modes/index.html.erb +1 -1
  91. data/app/views/hr_lite/approvals/index.html.erb +1 -1
  92. data/app/views/hr_lite/attendance/_month_grid.html.erb +9 -9
  93. data/app/views/hr_lite/attendance/_penalties.html.erb +2 -2
  94. data/app/views/hr_lite/attendance/_punch_card.html.erb +2 -4
  95. data/app/views/hr_lite/attendance/_slot.html.erb +9 -6
  96. data/app/views/hr_lite/attendance/_trail.html.erb +1 -1
  97. data/app/views/hr_lite/attendance/_work_mode.html.erb +1 -1
  98. data/app/views/hr_lite/attendance/show.html.erb +4 -4
  99. data/app/views/hr_lite/calendar/show.html.erb +2 -2
  100. data/app/views/hr_lite/comp_off_requests/index.html.erb +1 -1
  101. data/app/views/hr_lite/comp_off_requests/new.html.erb +1 -1
  102. data/app/views/hr_lite/documents/_upload_form.html.erb +1 -1
  103. data/app/views/hr_lite/employee_profiles/show.html.erb +4 -2
  104. data/app/views/hr_lite/home/index.html.erb +2 -2
  105. data/app/views/hr_lite/id_cards/show.html.erb +5 -1
  106. data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +2 -2
  107. data/app/views/hr_lite/leave_requests/new.html.erb +17 -8
  108. data/app/views/hr_lite/leave_requests/show.html.erb +2 -0
  109. data/app/views/hr_lite/org/show.html.erb +1 -1
  110. data/app/views/hr_lite/regularization_requests/index.html.erb +7 -7
  111. data/app/views/hr_lite/regularization_requests/new.html.erb +7 -5
  112. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +2 -2
  113. data/app/views/hr_lite/salary_slips/pdf.html.erb +1 -1
  114. data/app/views/hr_lite/tax/_computation.html.erb +1 -1
  115. data/app/views/hr_lite/tax_declarations/show.html.erb +4 -4
  116. data/app/views/hr_lite/team/show.html.erb +8 -7
  117. data/app/views/hr_lite/timelines/show.html.erb +1 -0
  118. data/app/views/hr_lite/wfh_requests/index.html.erb +3 -3
  119. data/app/views/hr_lite/work_time/_bars.html.erb +1 -1
  120. data/app/views/hr_lite/work_time/_donut.html.erb +1 -1
  121. data/app/views/hr_lite/work_time/_report.html.erb +14 -8
  122. data/app/views/layouts/hr_lite/application.html.erb +1 -0
  123. data/db/migrate/20261017161441_make_hr_lite_benefit_enrolments_unique_while_open.rb +8 -0
  124. data/db/migrate/20261017161443_add_attendance_record_id_index_to_hr_lite_work_mode_events.rb +5 -0
  125. data/lib/hr_lite/notifications.rb +5 -2
  126. data/lib/hr_lite/version.rb +1 -1
  127. metadata +4 -1
@@ -71,10 +71,8 @@ module HrLite
71
71
  deductions << { code: "pt", label: "Professional tax", amount: pt } if pt.positive?
72
72
 
73
73
  deductions.concat(extra_deductions)
74
- loan = loan_instalment
75
- deductions << loan if loan
76
74
 
77
- fy = SalarySlip.fy_to_date(@user, @run.period_month)
75
+ fy =SalarySlip.fy_to_date(@user, @run.period_month)
78
76
  tds = Calculators::Tds.call(
79
77
  regime: tax_regime,
80
78
  structure_monthly_gross: @structure.monthly_gross,
@@ -90,9 +88,16 @@ module HrLite
90
88
  rates: @rates[:income_tax],
91
89
  override: @tds_override
92
90
  )
91
+ loan = loan_instalment(gross_earned - deductions.sum(BigDecimal(0)) { |row| row[:amount] } - tds.monthly)
92
+ deductions << loan if loan
93
93
  deductions << { code: "tds", label: "Income tax (TDS)", amount: tds.monthly } if tds.monthly.positive?
94
94
 
95
95
  total_deductions = deductions.sum(BigDecimal(0)) { |row| row[:amount] }
96
+ # Claims booked onto this month: paid back with the salary but not
97
+ # salary, so outside gross and every PF, ESI, PT and TDS base.
98
+ claims = Expense.awaiting_reimbursement.where(user_id: @user.id, reimbursed_in: @run.period_month).includes(:category).map do |claim|
99
+ { code: "reimbursement", label: "#{claim.category.name} claim (reimbursement, outside gross)", amount: claim.amount }
100
+ end
96
101
 
97
102
  {
98
103
  period_month: @run.period_month,
@@ -104,7 +109,7 @@ module HrLite
104
109
  lop_days: Money.d(summary[:lop_days]),
105
110
  lop_override: @lop_override,
106
111
  tds_override: @tds_override,
107
- earnings: serialize_rows(earnings),
112
+ earnings: serialize_rows(earnings + claims),
108
113
  deductions: serialize_rows(deductions),
109
114
  employer_costs: employer_costs.transform_values { |v| v.to_s("F") }.to_json,
110
115
  tax_details: tds.details.to_json,
@@ -113,7 +118,7 @@ module HrLite
113
118
  # A heavy-LOP month can leave TDS (projected from the full structure)
114
119
  # larger than what was actually earned. Nobody is paid a negative
115
120
  # salary; PayrollRunProcessor warns when this floor bites.
116
- net_pay: [ gross_earned - total_deductions, BigDecimal(0) ].max,
121
+ net_pay: [ gross_earned + claims.sum(BigDecimal(0)) { |row| row[:amount] } - total_deductions, BigDecimal(0) ].max,
117
122
  computed_at: Time.current
118
123
  }
119
124
  end
@@ -172,13 +177,16 @@ module HrLite
172
177
  # The month's loan instalment, as one deduction line. Computed here and
173
178
  # BOOKED only when the run is finalized — a draft is recomputed freely,
174
179
  # and booking at compute would take a repayment on every pass.
175
- def loan_instalment
176
- total = Loan.active.where(user_id: @user.id).sum(BigDecimal(0)) do |loan|
180
+ # Never more than `room` (pay left after every other deduction); the rest stays owed.
181
+ def loan_instalment(room)
182
+ due = Loan.active.where(user_id: @user.id).sum(BigDecimal(0)) do |loan|
177
183
  loan.instalment_for(@run.period_month)
178
184
  end
179
- return nil unless total.positive?
185
+ return nil unless due.positive?
180
186
 
181
- { code: "loan_repayment", label: "Loan repayment", amount: total }
187
+ amount = room.clamp(BigDecimal(0), due)
188
+ row = { code: "loan_repayment", label: "Loan repayment", amount: amount }
189
+ amount < due ? row.merge(meta: { carried_forward: (due - amount).to_s("F") }) : row
182
190
  end
183
191
 
184
192
  # Months left in the Indian FY (Apr–Mar) including the run month itself:
@@ -16,8 +16,11 @@ module HrLite
16
16
  def self.for(user_id, at: Time.current) = for_users([ user_id ], at: at)[user_id]
17
17
 
18
18
  # When an open Busy or Meeting ends, or nil when the person is reachable.
19
- def self.do_not_disturb_until(user_id, at: Time.current)
20
- WorkModeEvent.open.overlay.where(user_id: user_id, mode: %w[busy meeting]).where("ends_at > ?", at).maximum(:ends_at)
19
+ def self.do_not_disturb_until(user_id, at: Time.current) = do_not_disturb_for([ user_id ], at: at)[user_id]
20
+
21
+ # The same for many people in one query: { user_id => ends_at }, reachable people absent.
22
+ def self.do_not_disturb_for(user_ids, at: Time.current)
23
+ WorkModeEvent.open.overlay.where(user_id: user_ids, mode: %w[busy meeting]).where("ends_at > ?", at).group(:user_id).maximum(:ends_at)
21
24
  end
22
25
 
23
26
  def self.for_users(user_ids, at: Time.current)
@@ -22,6 +22,9 @@
22
22
  <% end %>
23
23
 
24
24
  <section class="hrl-card">
25
+ <% if @assets.empty? %>
26
+ <p class="hrl-muted">No assets recorded yet. Add the first one above.</p>
27
+ <% else %>
25
28
  <div class="hrl-table-wrap">
26
29
  <table class="hrl-table hrl-table--stack">
27
30
  <thead><tr><th>Asset</th><th>Status</th><th>With</th><th></th></tr></thead>
@@ -39,13 +42,16 @@
39
42
  <td data-label="">
40
43
  <% if asset.assigned? %>
41
44
  <%= form_with url: hr_lite.take_back_admin_asset_path(asset), method: :post do %>
42
- <%= text_field_tag :condition_note, nil, placeholder: "Condition", class: "hrl-input" %>
45
+ <%= label_tag "asset-condition-#{asset.id}", "Condition on return", class: "hrl-sr-only" %>
46
+ <%= text_field_tag :condition_note, nil, id: "asset-condition-#{asset.id}", placeholder: "Condition on return", class: "hrl-input" %>
43
47
  <%= submit_tag "Take back", class: "hrl-btn" %>
44
48
  <% end %>
45
49
  <% elsif asset.available? %>
46
50
  <%= form_with url: hr_lite.assign_admin_asset_path(asset), method: :post do %>
51
+ <%= label_tag "asset-holder-#{asset.id}", "Hand over to", class: "hrl-sr-only" %>
47
52
  <%= select_tag :user_id,
48
- options_for_select(HrLite.employees.map { |u| [ hr_display_name(u), u.id ] }), class: "hrl-input",
53
+ options_for_select(HrLite.employees.map { |u| [ hr_display_name(u), u.id ] }), id: "asset-holder-#{asset.id}",
54
+ include_blank: "Choose…", required: true, class: "hrl-input",
49
55
  data: { hrl_filter: "Type a name to filter" } %>
50
56
  <%= submit_tag "Hand over", class: "hrl-btn" %>
51
57
  <% end %>
@@ -56,5 +62,6 @@
56
62
  </tbody>
57
63
  </table>
58
64
  </div>
65
+ <% end %>
59
66
  <%= hrl_pagination %>
60
67
  </section>
@@ -1,5 +1,6 @@
1
1
  <% content_for(:page_title) { "Attendance — #{hr_display_name(@employee)}" } %>
2
2
  <% content_for(:tabrow) { render "hr_lite/shared/person_tabs", user: @employee } %>
3
+ <% content_for(:back, "Team") %>
3
4
  <div class="hrl-page__head">
4
5
  <h1 class="hrl-page__title"><%= hr_display_name(@employee) %></h1>
5
6
  <div class="hrl-page__actions">
@@ -33,6 +34,7 @@
33
34
  <% if @punches.any? %>
34
35
  <section class="hrl-card">
35
36
  <h2 class="hrl-card__title">Where they punched</h2>
37
+ <% all_punches = HrLite::AttendancePunch.where(user_id: @employee.id, date: @punches.map(&:date)).order(:at, :id).group_by(&:date) # one query, not one a day %>
36
38
  <% @punches.each do |record| %>
37
39
  <div class="hrl-feed__item hrl-small">
38
40
  <div class="hrl-feed__body">
@@ -47,7 +49,7 @@
47
49
  <% end %>
48
50
  </div>
49
51
  <% end %>
50
- <% if record.check_in_place %><%= render "hr_lite/attendance/trail", record: record %><% end %>
52
+ <% if record.check_in_place %><%= render "hr_lite/attendance/trail", record: record, punches: all_punches.fetch(record.date, []) %><% end %>
51
53
  <% if (gap = record.punch_gap_m) %>
52
54
  <div class="<%= gap > HrLite::AttendanceRecord::PUNCH_GAP_ALERT_M ? "hrl-badge hrl-badge--warn" : "hrl-muted" %>"><%= (gap / 1000.0).round(1) %> km apart</div>
53
55
  <% end %>
@@ -25,14 +25,17 @@
25
25
  <% end %>
26
26
  </section>
27
27
 
28
+ <% fixes_pending = HrLite::RegularizationRequest.pending.where(user_id: @employee.id, date: @period.from..@period.to).pluck(:date) %>
28
29
  <section class="hrl-card">
29
30
  <h2 class="hrl-card__title">Days</h2>
30
31
  <% (@period.from..@period.to).each do |date| %>
31
- <% day = @days.for(date); rec = day.record; punches = @punches[date].to_a %>
32
+ <% day = @days.for(date); rec = day.record; punches = @punches[date].to_a
33
+ not_in_yet = date == Date.current && day.kind == :absent # the day is not over %>
32
34
  <div class="hrl-feed__item hrl-row">
33
35
  <div class="hrl-feed__body hrl-small">
34
36
  <strong><%= date.strftime("%a %-d %b") %></strong>
35
- <span class="hrl-badge <%= tones[day.kind] %>"><%= HrLite::ApplicationHelper::DAY_STATES[day.kind][1] %></span>
37
+ <span class="hrl-badge <%= not_in_yet ? "hrl-badge--muted" : tones[day.kind] %>"><%= not_in_yet ? "Not in yet" : HrLite::ApplicationHelper::DAY_STATES[day.kind][1] %></span>
38
+ <% if fixes_pending.include?(date) %><span class="hrl-badge hrl-badge--warn">Fix pending</span><% end %>
36
39
  <% if @profile&.hours_allowance_minutes && (length = HrLite::EmployeeProfile.day_minutes_for(@employee.id, on: date)) < HrLite::Setting.instance.day_minutes %>
37
40
  <span class="hrl-badge hrl-badge--muted">Day <%= hrl_duration(length * 60) %></span>
38
41
  <% end %>
@@ -1,6 +1,8 @@
1
+ <% content_for(:page_title) { "Audit trail" } %>
1
2
  <div class="hrl-page__head">
2
3
  <h1 class="hrl-page__title">Audit trail</h1>
3
- <p class="hrl-page__sub">Every policy, settings and payroll change — append-only.</p>
4
+ <p class="hrl-page__sub">Every policy, settings and payroll change — append-only.
5
+ <% if params[:views] %><a href="<%= hr_lite.admin_audit_logs_path %>">Hide page views</a><% else %><a href="<%= hr_lite.admin_audit_logs_path(views: 1) %>">Show page views too</a><% end %></p>
4
6
  </div>
5
7
 
6
8
  <% if @audit_logs.any? %>
@@ -13,15 +15,15 @@
13
15
  <% @audit_logs.each do |log| %>
14
16
  <tr>
15
17
  <td data-label="When"><%= log.created_at.strftime("%d %b %Y, %H:%M") %></td>
16
- <td data-label="Who"><%= hr_display_name(log.actor) %></td>
17
- <td data-label="Action"><span class="hrl-badge hrl-badge--muted"><%= log.action %></span></td>
18
+ <td data-label="Who"><%= log.actor ? hr_display_name(log.actor) : "System" %></td>
19
+ <td data-label="Action"><span class="hrl-badge hrl-badge--muted"><%= log.action.tr("._", " ").humanize %></span></td>
18
20
  <td data-label="Subject"><%= log.subject_type.demodulize.underscore.humanize %> #<%= log.subject_id %></td>
19
21
  <td data-label="Changes">
20
22
  <% log.audited_changes.each do |attr, change| %>
21
23
  <div class="hrl-small">
22
24
  <span class="hrl-muted"><%= attr.humanize %>:</span>
23
25
  <% if change.is_a?(Array) %>
24
- <%= change[0].inspect %> → <%= change[1].inspect %>
26
+ <%= change[0].to_s.presence || "blank" %> → <%= change[1].to_s.presence || "blank" %>
25
27
  <% else %>
26
28
  <%= change %>
27
29
  <% end %>
@@ -22,12 +22,12 @@
22
22
  </div>
23
23
 
24
24
  <div class="hrl-row hrl-row--mb" role="tablist">
25
- <a class="hrl-btn <%= "hrl-btn--primary" if @tab == "slips" %>" href="<%= hr_lite.admin_ca_reviews_path(keep.(tab: "slips", status: @status)) %>" aria-current="<%= @tab == "slips" ? "page" : nil %>">Salary slips</a>
26
- <a class="hrl-btn <%= "hrl-btn--primary" if @tab == "declarations" %>" href="<%= hr_lite.admin_ca_reviews_path(keep.(tab: "declarations", status: @status)) %>" aria-current="<%= @tab == "declarations" ? "page" : nil %>">Tax declarations</a>
25
+ <a class="hrl-btn" <%= "aria-current=true" if @tab == "slips" %> href="<%= hr_lite.admin_ca_reviews_path(keep.(tab: "slips", status: @status)) %>">Salary slips</a>
26
+ <a class="hrl-btn" <%= "aria-current=true" if @tab == "declarations" %> href="<%= hr_lite.admin_ca_reviews_path(keep.(tab: "declarations", status: @status)) %>">Tax declarations</a>
27
27
  </div>
28
28
  <div class="hrl-row hrl-row--mb">
29
29
  <% [ [ nil, "All" ], [ "unreviewed", "To review" ], [ "flagged", "Flagged" ], [ "verified", "Verified" ], [ "resolved", "Resolved" ] ].each do |value, label| %>
30
- <a class="hrl-btn <%= "hrl-btn--primary" if @status == value %>" href="<%= hr_lite.admin_ca_reviews_path(keep.(status: value)) %>"><%= label %></a>
30
+ <a class="hrl-btn" <%= "aria-current=true" if @status == value %> href="<%= hr_lite.admin_ca_reviews_path(keep.(status: value)) %>"><%= label %></a>
31
31
  <% end %>
32
32
  </div>
33
33
 
@@ -4,8 +4,7 @@
4
4
  <section class="hrl-card">
5
5
  <div class="hrl-row">
6
6
  <% HrLite::ChecklistTemplate::KINDS.each do |kind| %>
7
- <a class="hrl-btn <%= "hrl-btn--primary" if kind == @kind %>"
8
- href="<%= hr_lite.admin_checklists_path(kind: kind) %>"><%= kind.humanize %></a>
7
+ <%= link_to kind.humanize, hr_lite.admin_checklists_path(kind: kind), class: "hrl-btn", aria: { current: ("true" if kind == @kind) } %>
9
8
  <% end %>
10
9
  </div>
11
10
  </section>
@@ -37,7 +36,8 @@
37
36
  <td data-label="Due"><%= item.due_on&.strftime("%d %b %Y") || "—" %></td>
38
37
  <td data-label="">
39
38
  <%= form_with url: hr_lite.complete_admin_checklist_path(item), method: :post do %>
40
- <%= text_field_tag :note, nil, placeholder: "Note", class: "hrl-input" %>
39
+ <%= label_tag "checklist-note-#{item.id}", "Note (optional)", class: "hrl-sr-only" %>
40
+ <%= text_field_tag :note, nil, id: "checklist-note-#{item.id}", placeholder: "Note (optional)", class: "hrl-input" %>
41
41
  <%= submit_tag "Done", class: "hrl-btn hrl-btn--primary" %>
42
42
  <% end %>
43
43
  </td>
@@ -6,8 +6,7 @@
6
6
  <div class="hrl-row hrl-row--mb">
7
7
  <span class="hrl-small hrl-muted">Status</span>
8
8
  <% HrLite::CompOffRequest::STATUSES.each do |status| %>
9
- <a class="hrl-btn <%= 'hrl-btn--primary' if @status == status %>"
10
- href="<%= hr_lite.admin_comp_off_requests_path(status: status) %>"><%= status.humanize %></a>
9
+ <%= link_to status.humanize, hr_lite.admin_comp_off_requests_path(status: status), class: "hrl-btn", aria: { current: ("true" if @status == status) } %>
11
10
  <% end %>
12
11
  </div>
13
12
 
@@ -1,9 +1,7 @@
1
1
  <% content_for(:page_title) { "Comp-off — #{hr_display_name(@request.user)}" } %>
2
+ <a class="hrl-backlink" href="<%= hr_lite.admin_comp_off_requests_path %>"><span aria-hidden="true">‹</span> Approvals</a>
2
3
  <div class="hrl-page__head">
3
4
  <h1 class="hrl-page__title">Comp-off — <%= hr_display_name(@request.user) %></h1>
4
- <div class="hrl-page__actions">
5
- <a class="hrl-btn" href="<%= hr_lite.admin_comp_off_requests_path %>">All requests</a>
6
- </div>
7
5
  </div>
8
6
 
9
7
  <section class="hrl-card">
@@ -34,7 +32,8 @@
34
32
  `formaction:` can never pass verification on the second endpoint. %>
35
33
  <section class="hrl-card">
36
34
  <h2 class="hrl-card__title">Approve</h2>
37
- <%= form_with url: hr_lite.approve_admin_comp_off_request_path(@request), method: :post, local: true do |f| %>
35
+ <%= form_with url: hr_lite.approve_admin_comp_off_request_path(@request), method: :post, local: true,
36
+ html: { data: { turbo_confirm: "Credit #{hrl_days(@request.credit_days)} day of comp-off to #{hr_display_name(@request.user)}?" } } do |f| %>
38
37
  <div class="hrl-field">
39
38
  <%= f.label :decision_note, "Note (optional)", for: "approve_note" %>
40
39
  <%= f.text_area :decision_note, id: "approve_note" %>
@@ -48,7 +47,8 @@
48
47
 
49
48
  <section class="hrl-card">
50
49
  <h2 class="hrl-card__title">Reject</h2>
51
- <%= form_with url: hr_lite.reject_admin_comp_off_request_path(@request), method: :post, local: true do |f| %>
50
+ <%= form_with url: hr_lite.reject_admin_comp_off_request_path(@request), method: :post, local: true,
51
+ html: { data: { turbo_confirm: "Reject #{hr_display_name(@request.user)}'s comp-off for #{@request.date_worked.strftime('%d %b %Y')}?" } } do |f| %>
52
52
  <div class="hrl-field">
53
53
  <%= f.label :decision_note, "Reason (required)", for: "reject_note" %>
54
54
  <%= f.text_area :decision_note, id: "reject_note", required: true %>
@@ -1,6 +1,6 @@
1
- <% content_for(:page_title) { "Role change" } %>
1
+ <% content_for(:page_title) { "Designation change" } %>
2
2
  <div class="hrl-page__head">
3
- <h1 class="hrl-page__title">Role change — <%= hr_display_name(@profile.user) %></h1>
3
+ <h1 class="hrl-page__title">Designation change — <%= hr_display_name(@profile.user) %></h1>
4
4
  </div>
5
5
 
6
6
  <section class="hrl-card">
@@ -12,7 +12,8 @@
12
12
  <div class="hrl-field"><%= f.label :effective_date %><%= f.date_field :effective_date %></div>
13
13
  <div class="hrl-field"><%= f.label :note, "Note (optional)" %><%= f.text_area :note %></div>
14
14
  <div class="hrl-form-actions">
15
- <%= f.submit "Record change", class: "hrl-btn hrl-btn--primary" %>
15
+ <%= f.submit "Record change", class: "hrl-btn hrl-btn--primary",
16
+ data: { turbo_confirm: "Record this designation change? #{hr_display_name(@profile.user)} is told at once." } %>
16
17
  <a class="hrl-btn" href="<%= hr_lite.admin_employee_path(@profile) %>">Cancel</a>
17
18
  </div>
18
19
  <% end %>
@@ -8,10 +8,10 @@
8
8
  visibility allows.
9
9
  </p>
10
10
  <div class="hrl-row">
11
- <a class="hrl-btn <%= "hrl-btn--primary" if @category.nil? %>"
11
+ <a class="hrl-btn" <%= "aria-current=true" if @category.nil? %>
12
12
  href="<%= hr_lite.admin_documents_path %>">All</a>
13
13
  <% @categories.each do |category| %>
14
- <a class="hrl-btn <%= "hrl-btn--primary" if @category == category %>"
14
+ <a class="hrl-btn" <%= "aria-current=true" if @category == category %>
15
15
  href="<%= hr_lite.admin_documents_path(category: category) %>"><%= category.humanize %></a>
16
16
  <% end %>
17
17
  </div>
@@ -44,7 +44,8 @@
44
44
  <%= button_to "Verify", hr_lite.verify_admin_document_path(doc),
45
45
  method: :post, class: "hrl-btn hrl-btn--primary" %>
46
46
  <%= form_with url: hr_lite.reject_admin_document_path(doc), method: :post, local: true do %>
47
- <%= text_field_tag :note, nil, placeholder: "What is wrong with it", class: "hrl-input", required: true %>
47
+ <%= label_tag "reject-doc-#{doc.id}", "What is wrong with it", class: "hrl-sr-only" %>
48
+ <%= text_field_tag :note, nil, id: "reject-doc-#{doc.id}", placeholder: "What is wrong with it", class: "hrl-input", required: true %>
48
49
  <%= submit_tag "Reject", class: "hrl-btn" %>
49
50
  <% end %>
50
51
  </td>
@@ -58,6 +59,9 @@
58
59
 
59
60
  <section class="hrl-card">
60
61
  <h2 class="hrl-card__title">Everything on file</h2>
62
+ <% if @documents.empty? %>
63
+ <p class="hrl-muted">No documents on file yet. People upload them from Me › Documents; you can upload for someone from their page.</p>
64
+ <% else %>
61
65
  <div class="hrl-table-wrap">
62
66
  <table class="hrl-table hrl-table--stack">
63
67
  <thead><tr><th>Person</th><th>Document</th><th>Expires</th><th>Checked</th></tr></thead>
@@ -85,5 +89,6 @@
85
89
  </tbody>
86
90
  </table>
87
91
  </div>
92
+ <% end %>
88
93
  <%= hrl_pagination %>
89
94
  </section>
@@ -1,5 +1,6 @@
1
1
  <% content_for(:page_title) { hr_display_name(@employee) } %>
2
2
  <% content_for(:tabrow) { render "hr_lite/shared/person_tabs", user: @employee } %>
3
+ <% content_for(:back, "Documents to verify") %>
3
4
  <div class="hrl-page__head">
4
5
  <h1 class="hrl-page__title"><%= hr_display_name(@employee) %> — documents</h1>
5
6
  </div>
@@ -31,7 +32,7 @@
31
32
  </td>
32
33
  <td data-label="">
33
34
  <% if doc.readable_by?(hr_current_user) && doc.file.attached? %>
34
- <a class="hrl-small" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
35
+ <a class="hrl-btn" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
35
36
  <% end %>
36
37
  </td>
37
38
  </tr>
@@ -1,6 +1,7 @@
1
1
  <section class="hrl-card">
2
2
  <%= form_with model: profile, url: url, method: method, scope: :employee_profile, local: true do |f| %>
3
3
  <%= render "hr_lite/shared/form_errors", record: profile %>
4
+ <h2 class="hrl-card__title">Identity</h2>
4
5
  <% if profile.new_record? && profile.user_id.blank? %>
5
6
  <div class="hrl-field">
6
7
  <%= f.label :user_id, "Existing staff member" %>
@@ -22,7 +23,7 @@
22
23
  </div>
23
24
  <div class="hrl-field">
24
25
  <%= f.label :new_user_password, "Starting password (optional)" %>
25
- <%= f.text_field :new_user_password %>
26
+ <%= f.password_field :new_user_password, autocomplete: "new-password" %>
26
27
  <p class="hrl-hint">Leave blank (recommended): they get an email invite and set their own
27
28
  password. Fill it only if you want to hand a password over yourself.</p>
28
29
  </div>
@@ -40,6 +41,7 @@
40
41
  <p class="hrl-hint">The employee code is assigned automatically
41
42
  (<%= HrLite::Setting.instance.employee_code_prefix %> + next number).</p>
42
43
  <% end %>
44
+ <h2 class="hrl-card__title hrl-mt">Work</h2>
43
45
  <div class="hrl-field"><%= f.label :designation %><%= f.text_field :designation %></div>
44
46
  <div class="hrl-field">
45
47
  <%= f.label :manager_id, "Reports to (their L1)" %>
@@ -64,7 +66,7 @@
64
66
  <%= f.select :office_location_id, offices, { include_blank: "None — only all-office holidays" }, data: { hrl_filter: ("Type an office" if offices.size > 5) } %></div>
65
67
  <% end %>
66
68
  <div class="hrl-field"><%= f.label :date_of_birth %><%= f.date_field :date_of_birth %></div>
67
- <div class="hrl-field"><%= f.label :date_of_joining %><%= f.date_field :date_of_joining %></div>
69
+ <div class="hrl-field"><%= f.label :date_of_joining %><%= f.date_field :date_of_joining, required: true %></div>
68
70
  <div class="hrl-field">
69
71
  <%= f.label :blood_group, id: "hrl-blood-group", for: nil %>
70
72
  <div class="hrl-chiprow" role="radiogroup" aria-labelledby="hrl-blood-group">
@@ -79,6 +81,7 @@
79
81
  <%= f.label :probation_until, "On probation until (blank = not on probation)" %>
80
82
  <%= f.date_field :probation_until %>
81
83
  </div>
84
+ <h2 class="hrl-card__title hrl-mt">Attendance rules</h2>
82
85
  <% if HrLite.config.attendance_slots %>
83
86
  <div class="hrl-field">
84
87
  <%= f.label :buffer_minutes, "Late buffer (delays late penalties; colleagues see the reason category)" %>
@@ -99,12 +102,16 @@
99
102
  <%= f.label :date_of_exit, "Date of exit (leave blank while employed)" %>
100
103
  <%= f.date_field :date_of_exit %>
101
104
  </div>
102
- <div class="hrl-field"><%= f.label :pan_number, "PAN" %><%= f.text_field :pan_number %></div>
103
- <div class="hrl-field"><%= f.label :pf_uan, "PF UAN" %><%= f.text_field :pf_uan %></div>
105
+ <h2 class="hrl-card__title hrl-mt">Pay &amp; statutory</h2>
106
+ <div class="hrl-field"><%= f.label :pan_number, "PAN" %><%= f.text_field :pan_number %>
107
+ <p class="hrl-hint">10 characters in capitals, like ABCDE1234F.</p></div>
108
+ <div class="hrl-field"><%= f.label :pf_uan, "PF UAN" %><%= f.text_field :pf_uan %>
109
+ <p class="hrl-hint">12 digits.</p></div>
104
110
  <div class="hrl-field"><%= f.label :esi_number, "ESI number" %><%= f.text_field :esi_number %></div>
105
111
  <div class="hrl-field"><%= f.label :bank_name %><%= f.text_field :bank_name %></div>
106
112
  <div class="hrl-field"><%= f.label :bank_account_number %><%= f.text_field :bank_account_number %></div>
107
- <div class="hrl-field"><%= f.label :bank_ifsc, "IFSC" %><%= f.text_field :bank_ifsc %></div>
113
+ <div class="hrl-field"><%= f.label :bank_ifsc, "IFSC" %><%= f.text_field :bank_ifsc %>
114
+ <p class="hrl-hint">11 characters in capitals, fifth is a zero, like HDFC0001234.</p></div>
108
115
  <div class="hrl-field">
109
116
  <%= f.label :tax_regime %>
110
117
  <%= f.select :tax_regime, HrLite::EmployeeProfile::TAX_REGIMES.map { |r| [ "#{r.humanize} regime", r ] } %>
@@ -1,17 +1,20 @@
1
1
  <% content_for(:page_title) { @profile.employee_code } %>
2
2
  <% content_for(:tabrow) { render "hr_lite/shared/person_tabs", user: @profile.user } %>
3
+ <% content_for(:back, "Employees") %>
4
+ <% own = @profile.user_id == hr_current_user.id && !hr_superadmin? # your own pay and record are for someone else to change, unless you are Super Admin %>
3
5
  <div class="hrl-page__head">
4
6
  <h1 class="hrl-page__title"><%= hr_display_name(@profile.user) %> <span class="hrl-muted">(<%= @profile.employee_code %>)</span></h1>
5
7
  <div class="hrl-page__actions">
8
+ <% unless own %>
6
9
  <a class="hrl-btn" href="<%= hr_lite.edit_admin_employee_path(@profile) %>">Edit</a>
7
- <%# Their records are the tabs above; the HR actions fold into one Manage button. %>
10
+ <%# Their records are the tabs above; the HR actions fold into one More actions button. %>
8
11
  <% if hr_superadmin? || hr_can?("salary.manage", scope: :all) %>
9
12
  <details>
10
- <summary class="hrl-btn">Manage</summary>
13
+ <summary class="hrl-btn">More actions (appraisal, designation change, PIP, salary)</summary>
11
14
  <div class="hrl-row">
12
15
  <% if hr_superadmin? %>
13
16
  <a class="hrl-btn" href="<%= hr_lite.new_admin_employee_appraisal_path(@profile) %>">New appraisal</a>
14
- <a class="hrl-btn" href="<%= hr_lite.new_admin_employee_designation_change_path(@profile) %>">Role change</a>
17
+ <a class="hrl-btn" href="<%= hr_lite.new_admin_employee_designation_change_path(@profile) %>">Designation change</a>
15
18
  <a class="hrl-btn" href="<%= hr_lite.new_admin_performance_plan_path(user_id: @profile.user_id) %>">Start PIP</a>
16
19
  <% end %>
17
20
  <% if hr_can?("salary.manage", scope: :all) %>
@@ -20,6 +23,7 @@
20
23
  </div>
21
24
  </details>
22
25
  <% end %>
26
+ <% end %>
23
27
  </div>
24
28
  </div>
25
29
 
@@ -48,34 +52,6 @@
48
52
  </section>
49
53
  <% end %>
50
54
 
51
- <%# Stays on the page after an exit date is stamped. Accepting a resignation
52
- stamps one, and hiding this card at that moment removed the only control
53
- that revokes sign-in — during the notice period, when it is needed. %>
54
- <section class="hrl-card">
55
- <h2 class="hrl-card__title"><%= @profile.date_of_exit ? "Revoke access" : "Offboard" %></h2>
56
- <% if @profile.date_of_exit %>
57
- <p class="hrl-small hrl-muted">Last day is <%= @profile.date_of_exit.strftime("%d %b %Y") %> —
58
- attendance and payroll already clip to it. Run this on or after that day to revoke
59
- sign-in. Records are never deleted.</p>
60
- <% else %>
61
- <p class="hrl-small hrl-muted">Stamps the last day (attendance and payroll clip to it) and revokes sign-in.
62
- Records are never deleted.</p>
63
- <% end %>
64
- <%= form_with url: hr_lite.offboard_admin_employee_path(@profile), method: :post, local: true,
65
- html: { data: { turbo_confirm: "Offboard #{hr_display_name(@profile.user)}?" } } do %>
66
- <div class="hrl-field">
67
- <label for="off_date">Last working day</label>
68
- <input type="date" id="off_date" name="date_of_exit" class="hrl-input"
69
- value="<%= @profile.date_of_exit || Date.current %>">
70
- </div>
71
- <div class="hrl-form-actions">
72
- <button class="hrl-btn hrl-btn--danger" type="submit">
73
- <%= @profile.date_of_exit ? "Revoke access now" : "Offboard employee" %>
74
- </button>
75
- </div>
76
- <% end %>
77
- </section>
78
-
79
55
  <div class="hrl-grid hrl-grid--2">
80
56
  <section class="hrl-card">
81
57
  <h2 class="hrl-card__title">Profile</h2>
@@ -107,7 +83,7 @@
107
83
  <td data-label="Effective"><%= structure.effective_from.strftime("%b %Y") %></td>
108
84
  <td data-label="Basic" class="hrl-num"><%= hrl_money(structure.basic) %></td>
109
85
  <td data-label="Gross" class="hrl-num"><%= hrl_money(structure.monthly_gross) %></td>
110
- <td data-label=""><a class="hrl-btn" href="<%= hr_lite.edit_admin_employee_salary_structure_path(@profile, structure) %>">Edit</a></td>
86
+ <td data-label=""><% unless own %><a class="hrl-btn" href="<%= hr_lite.edit_admin_employee_salary_structure_path(@profile, structure) %>">Edit</a><% end %></td>
111
87
  </tr>
112
88
  <% end %>
113
89
  </tbody>
@@ -146,3 +122,33 @@
146
122
  <% end %>
147
123
  </section>
148
124
  <% end %>
125
+
126
+ <%# Stays on the page after an exit date is stamped. Accepting a resignation
127
+ stamps one, and hiding this card at that moment removed the only control
128
+ that revokes sign-in — during the notice period, when it is needed. %>
129
+ <% unless own %>
130
+ <section class="hrl-card">
131
+ <h2 class="hrl-card__title"><%= @profile.date_of_exit ? "Revoke access" : "Offboard" %></h2>
132
+ <% if @profile.date_of_exit %>
133
+ <p class="hrl-small hrl-muted">Last day is <%= @profile.date_of_exit.strftime("%d %b %Y") %> —
134
+ attendance and payroll already clip to it. Run this on or after that day to revoke
135
+ sign-in. Records are never deleted.</p>
136
+ <% else %>
137
+ <p class="hrl-small hrl-muted">Stamps the last day (attendance and payroll clip to it) and revokes sign-in.
138
+ Records are never deleted.</p>
139
+ <% end %>
140
+ <%= form_with url: hr_lite.offboard_admin_employee_path(@profile), method: :post, local: true,
141
+ html: { data: { turbo_confirm: "Offboard #{hr_display_name(@profile.user)}?" } } do %>
142
+ <div class="hrl-field">
143
+ <label for="off_date">Last working day</label>
144
+ <input type="date" id="off_date" name="date_of_exit" class="hrl-input"
145
+ value="<%= @profile.date_of_exit || Date.current %>">
146
+ </div>
147
+ <div class="hrl-form-actions">
148
+ <button class="hrl-btn hrl-btn--danger" type="submit">
149
+ <%= @profile.date_of_exit ? "Revoke access now" : "Offboard employee" %>
150
+ </button>
151
+ </div>
152
+ <% end %>
153
+ </section>
154
+ <% end %>
@@ -4,7 +4,7 @@
4
4
  <section class="hrl-card">
5
5
  <div class="hrl-row">
6
6
  <% HrLite::Expense::STATUSES.each do |status| %>
7
- <a class="hrl-btn <%= "hrl-btn--primary" if status == @status %>"
7
+ <a class="hrl-btn" <%= "aria-current=true" if status == @status %>
8
8
  href="<%= hr_lite.admin_expenses_path(status: status) %>"><%= status.humanize %></a>
9
9
  <% end %>
10
10
  </div>
@@ -41,8 +41,9 @@
41
41
  <% end %>
42
42
  <% elsif expense.approved? %>
43
43
  <%= form_with url: hr_lite.reimburse_admin_expense_path(expense), method: :post do %>
44
- <%= text_field_tag :period_month, Date.current.strftime("%Y-%m"), size: 8, class: "hrl-input" %>
45
- <%= submit_tag "Mark reimbursed", class: "hrl-btn" %>
44
+ <%= label_tag "reimburse-month-#{expense.id}", "Paid with the payroll of", class: "hrl-sr-only" %>
45
+ <%= month_field_tag :period_month, (expense.reimbursed_in || Date.current).strftime("%Y-%m"), id: "reimburse-month-#{expense.id}", required: true, class: "hrl-input" %>
46
+ <%= submit_tag "Pay with payroll", class: "hrl-btn" %>
46
47
  <% end %>
47
48
  <% end %>
48
49
  </td>
@@ -2,8 +2,9 @@
2
2
  <div class="hrl-page__head">
3
3
  <h1 class="hrl-page__title">Holidays <%= @year %></h1>
4
4
  <div class="hrl-page__actions">
5
- <a class="hrl-btn" href="<%= hr_lite.admin_holidays_path(year: @year - 1) %>"><%= @year - 1 %></a>
6
- <a class="hrl-btn" href="<%= hr_lite.admin_holidays_path(year: @year + 1) %>"><%= @year + 1 %></a>
5
+ <a class="hrl-btn hrl-btn--primary" href="#add-holiday">Add a holiday</a>
6
+ <a class="hrl-btn" href="<%= hr_lite.admin_holidays_path(year: @year - 1) %>">‹ <%= @year - 1 %></a>
7
+ <a class="hrl-btn" href="<%= hr_lite.admin_holidays_path(year: @year + 1) %>"><%= @year + 1 %> ›</a>
7
8
  </div>
8
9
  </div>
9
10
 
@@ -35,7 +36,7 @@
35
36
  </section>
36
37
 
37
38
  <div class="hrl-grid hrl-grid--2">
38
- <section class="hrl-card">
39
+ <section class="hrl-card" id="add-holiday">
39
40
  <h2 class="hrl-card__title">Add one</h2>
40
41
  <%= form_with model: @holiday, url: hr_lite.admin_holidays_path, local: true do |f| %>
41
42
  <div class="hrl-field"><%= f.label :date %><%= f.date_field :date %></div>
@@ -62,7 +63,7 @@
62
63
  <textarea name="lines" id="bulk-lines" class="hrl-input" rows="6" placeholder="2026-11-09, Diwali&#10;2026-03-04, Holi"></textarea>
63
64
  </div>
64
65
  <div class="hrl-form-actions">
65
- <button type="submit" class="hrl-btn hrl-btn--primary">Add all</button>
66
+ <button type="submit" class="hrl-btn">Add all</button>
66
67
  </div>
67
68
  <% end %>
68
69
  </section>
@@ -4,7 +4,7 @@
4
4
  <section class="hrl-card">
5
5
  <div class="hrl-row">
6
6
  <% HrLite::HrRequest::STATUSES.each do |status| %>
7
- <a class="hrl-btn <%= "hrl-btn--primary" if status == @status %>"
7
+ <a class="hrl-btn" <%= "aria-current=true" if status == @status %>
8
8
  href="<%= hr_lite.admin_hr_requests_path(status: status) %>"><%= status.humanize %></a>
9
9
  <% end %>
10
10
  </div>
@@ -4,7 +4,7 @@
4
4
  <section class="hrl-card">
5
5
  <p class="hrl-small hrl-muted">
6
6
  <%= @request.category_label %> · <%= hr_display_name(@request.user) %> ·
7
- <%= @request.created_at.to_date.strftime("%d %b %Y") %> ·
7
+ <%= @request.created_at.to_date.strftime("%d %b %Y") %>
8
8
  <%= hrl_request_status_badge(@request.status) %>
9
9
  </p>
10
10
  <% if @request.category == "id_card" %>
@@ -20,7 +20,7 @@
20
20
  <h2 class="hrl-card__title">Answer</h2>
21
21
  <%= form_with url: hr_lite.resolve_admin_hr_request_path(@request), method: :post do %>
22
22
  <div class="hrl-field">
23
- <%= label_tag :resolution, "What you did" %>
23
+ <%= label_tag :resolution, "Answer to #{hr_display_name(@request.user)}" %>
24
24
  <%= text_area_tag :resolution, nil, rows: 4, required: true %>
25
25
  </div>
26
26
  <div class="hrl-form-actions">
@@ -33,7 +33,8 @@
33
33
  <%= label_tag :assignee_id, "Or hand it to somebody" %>
34
34
  <%= select_tag :assignee_id,
35
35
  options_for_select(HrLite.users_holding("hr_request.manage")
36
- .map { |u| [ hr_display_name(u), u.id ] }) %>
36
+ .map { |u| [ hr_display_name(u), u.id ] }),
37
+ include_blank: "Choose…", required: true, data: { hrl_filter: "Type a name to filter" } %>
37
38
  </div>
38
39
  <div class="hrl-form-actions">
39
40
  <%= submit_tag "Assign", class: "hrl-btn" %>