hr_lite 0.21.3 → 0.22.1

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/README.md +54 -2
  4. data/app/assets/javascripts/hr_lite/confirm.js +18 -3
  5. data/app/assets/javascripts/hr_lite/geo_punch.js +65 -10
  6. data/app/assets/javascripts/hr_lite/nav.js +17 -0
  7. data/app/assets/javascripts/hr_lite/select_filter.js +46 -0
  8. data/app/assets/stylesheets/hr_lite/hr_lite.css +219 -79
  9. data/app/controllers/hr_lite/admin/assets_controller.rb +1 -1
  10. data/app/controllers/hr_lite/admin/attendances_controller.rb +31 -0
  11. data/app/controllers/hr_lite/approvals_controller.rb +1 -12
  12. data/app/controllers/hr_lite/attendance_controller.rb +7 -0
  13. data/app/controllers/hr_lite/holidays_controller.rb +1 -0
  14. data/app/controllers/hr_lite/home_controller.rb +40 -3
  15. data/app/controllers/hr_lite/hr_requests_controller.rb +1 -1
  16. data/app/controllers/hr_lite/leave_requests_controller.rb +13 -2
  17. data/app/controllers/hr_lite/regularization_requests_controller.rb +10 -2
  18. data/app/controllers/hr_lite/tax_declarations_controller.rb +4 -0
  19. data/app/helpers/hr_lite/application_helper.rb +43 -30
  20. data/app/jobs/hr_lite/attendance_close_job.rb +6 -1
  21. data/app/models/hr_lite/approval.rb +5 -0
  22. data/app/models/hr_lite/attendance_record.rb +26 -0
  23. data/app/models/hr_lite/employee_profile.rb +1 -0
  24. data/app/models/hr_lite/hr_request.rb +2 -1
  25. data/app/models/hr_lite/office_location.rb +12 -0
  26. data/app/models/hr_lite/regularization_request.rb +10 -4
  27. data/app/services/hr_lite/attendance_puncher.rb +8 -3
  28. data/app/views/hr_lite/admin/assets/index.html.erb +3 -2
  29. data/app/views/hr_lite/admin/assets/new.html.erb +6 -2
  30. data/app/views/hr_lite/admin/attendances/index.html.erb +37 -6
  31. data/app/views/hr_lite/admin/attendances/show.html.erb +11 -7
  32. data/app/views/hr_lite/admin/attendances/week.html.erb +42 -0
  33. data/app/views/hr_lite/admin/checklists/index.html.erb +1 -1
  34. data/app/views/hr_lite/admin/comp_off_requests/index.html.erb +2 -2
  35. data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +2 -2
  36. data/app/views/hr_lite/admin/documents/index.html.erb +1 -1
  37. data/app/views/hr_lite/admin/employees/_form.html.erb +15 -7
  38. data/app/views/hr_lite/admin/employees/show.html.erb +5 -5
  39. data/app/views/hr_lite/admin/expenses/index.html.erb +2 -2
  40. data/app/views/hr_lite/admin/leave_balances/index.html.erb +3 -3
  41. data/app/views/hr_lite/admin/leave_requests/index.html.erb +2 -2
  42. data/app/views/hr_lite/admin/leave_requests/show.html.erb +4 -6
  43. data/app/views/hr_lite/admin/loans/new.html.erb +2 -1
  44. data/app/views/hr_lite/admin/overview/index.html.erb +3 -3
  45. data/app/views/hr_lite/admin/regularization_requests/index.html.erb +1 -1
  46. data/app/views/hr_lite/admin/shared/_approvals_tabs.html.erb +6 -5
  47. data/app/views/hr_lite/appraisals/show.html.erb +1 -1
  48. data/app/views/hr_lite/approvals/index.html.erb +1 -1
  49. data/app/views/hr_lite/attendance/_month_grid.html.erb +21 -26
  50. data/app/views/hr_lite/attendance/_punch_card.html.erb +32 -10
  51. data/app/views/hr_lite/attendance/show.html.erb +36 -7
  52. data/app/views/hr_lite/benefits/index.html.erb +1 -1
  53. data/app/views/hr_lite/calendar/show.html.erb +29 -22
  54. data/app/views/hr_lite/comp_off_requests/index.html.erb +3 -3
  55. data/app/views/hr_lite/comp_off_requests/new.html.erb +3 -2
  56. data/app/views/hr_lite/documents/index.html.erb +32 -30
  57. data/app/views/hr_lite/employee_profiles/show.html.erb +2 -2
  58. data/app/views/hr_lite/expenses/index.html.erb +17 -35
  59. data/app/views/hr_lite/expenses/new.html.erb +11 -6
  60. data/app/views/hr_lite/holidays/_next_holiday.html.erb +11 -8
  61. data/app/views/hr_lite/holidays/index.html.erb +6 -6
  62. data/app/views/hr_lite/home/index.html.erb +29 -19
  63. data/app/views/hr_lite/hr_requests/index.html.erb +7 -24
  64. data/app/views/hr_lite/hr_requests/new.html.erb +7 -5
  65. data/app/views/hr_lite/hr_requests/show.html.erb +4 -0
  66. data/app/views/hr_lite/kudos/index.html.erb +4 -3
  67. data/app/views/hr_lite/leave_balances/index.html.erb +14 -5
  68. data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +5 -3
  69. data/app/views/hr_lite/leave_requests/_out_list.html.erb +16 -0
  70. data/app/views/hr_lite/leave_requests/index.html.erb +8 -21
  71. data/app/views/hr_lite/leave_requests/new.html.erb +22 -9
  72. data/app/views/hr_lite/leave_requests/show.html.erb +3 -5
  73. data/app/views/hr_lite/loans/index.html.erb +18 -20
  74. data/app/views/hr_lite/policies/index.html.erb +1 -1
  75. data/app/views/hr_lite/regularization_requests/index.html.erb +2 -2
  76. data/app/views/hr_lite/regularization_requests/new.html.erb +11 -8
  77. data/app/views/hr_lite/resignations/show.html.erb +1 -1
  78. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +9 -3
  79. data/app/views/hr_lite/salary_slips/index.html.erb +48 -44
  80. data/app/views/hr_lite/shared/_bell.html.erb +1 -1
  81. data/app/views/hr_lite/shared/_form_errors.html.erb +1 -1
  82. data/app/views/hr_lite/tax_declarations/show.html.erb +24 -19
  83. data/app/views/hr_lite/team/show.html.erb +2 -2
  84. data/app/views/layouts/hr_lite/application.html.erb +24 -8
  85. data/config/routes.rb +3 -1
  86. data/lib/hr_lite/configuration.rb +16 -2
  87. data/lib/hr_lite/notifications.rb +1 -0
  88. data/lib/hr_lite/version.rb +1 -1
  89. metadata +4 -1
@@ -1,45 +1,52 @@
1
1
  <% content_for(:page_title) { "Calendar" } %>
2
2
  <div class="hrl-page__head">
3
3
  <h1 class="hrl-page__title">Company calendar</h1>
4
+ <div class="hrl-page__actions">
5
+ <a class="hrl-btn" href="<%= hr_lite.holidays_path %>">Holiday list</a>
6
+ </div>
4
7
  </div>
5
8
 
6
9
  <section class="hrl-card">
7
- <div class="hrl-row" style="margin-bottom:.6rem;">
8
- <a class="hrl-btn" href="<%= hr_lite.calendar_path(month: (@month - 1.month).strftime("%Y-%m")) %>">&larr;</a>
10
+ <% prev_m, next_m = @month - 1.month, @month + 1.month %>
11
+ <div class="hrl-row hrl-row--mb">
12
+ <a class="hrl-btn" href="<%= hr_lite.calendar_path(month: prev_m.strftime("%Y-%m")) %>" aria-label="Previous month, <%= prev_m.strftime("%B %Y") %>"><span aria-hidden="true">&lsaquo;</span> <%= prev_m.strftime("%b") %></a>
9
13
  <strong><%= @month.strftime("%B %Y") %></strong>
10
- <a class="hrl-btn" href="<%= hr_lite.calendar_path(month: (@month + 1.month).strftime("%Y-%m")) %>">&rarr;</a>
11
- <a class="hrl-btn hrl-right" href="<%= hr_lite.holidays_path %>">Holiday list</a>
14
+ <a class="hrl-btn" href="<%= hr_lite.calendar_path(month: next_m.strftime("%Y-%m")) %>" aria-label="Next month, <%= next_m.strftime("%B %Y") %>"><%= next_m.strftime("%b") %> <span aria-hidden="true">&rsaquo;</span></a>
12
15
  </div>
13
16
 
14
17
  <% first = @month.beginning_of_month %>
15
- <div class="hrl-mgrid">
18
+ <div class="hrl-mgrid hrl-mgrid--cal">
16
19
  <% %w[Mo Tu We Th Fr Sa Su].each do |d| %><div class="hrl-mgrid__head"><%= d %></div><% end %>
17
20
  <% (first.cwday - 1).times do %><div></div><% end %>
18
21
  <% (first..@month.end_of_month).each do |date| %>
19
- <% cell_class =
20
- if @holidays[date] then "hrl-mgrid__day--holiday"
22
+ <% cell_class, code =
23
+ if @holidays[date] then [ "hrl-mgrid__day--holiday", "H" ]
21
24
  elsif @calendar.weekend?(date) then "hrl-mgrid__day--weekend"
22
- elsif @leaves[date].any? then "hrl-mgrid__day--leave"
25
+ elsif @leaves[date].any? then [ "hrl-mgrid__day--leave", "#{@leaves[date].size} off" ]
23
26
  end %>
24
- <div class="hrl-mgrid__day <%= cell_class %>">
27
+ <div class="<%= class_names("hrl-mgrid__day", cell_class, "hrl-mgrid__day--today": date == Date.current) %>">
25
28
  <span class="hrl-mgrid__num"><%= date.day %></span>
26
- <% Array(@holidays[date]).each do |holiday| %>
27
- <span class="hrl-mgrid__tag" title="<%= holiday.name %>"><%= holiday.name %></span>
28
- <% end %>
29
- <% @leaves[date].first(2).each do |request| %>
30
- <span class="hrl-mgrid__tag" style="color: <%= request.leave_type.color %>;"
31
- title="<%= hr_display_name(request.user) %> — <%= request.leave_type.name %>">
32
- <%= hr_display_name(request.user).split.first %> · <%= request.leave_type.code %>
33
- </span>
34
- <% end %>
35
- <% if @leaves[date].size > 2 %><span class="hrl-mgrid__tag">+<%= @leaves[date].size - 2 %> more</span><% end %>
29
+ <% if code %><span class="hrl-mgrid__tag"><%= code %></span><% end %>
36
30
  </div>
37
31
  <% end %>
38
32
  </div>
39
33
 
40
34
  <div class="hrl-legend hrl-small">
41
- <span class="hrl-badge hrl-badge--warn">Holiday</span>
42
- <span class="hrl-badge">On leave</span>
43
- <span class="hrl-badge hrl-badge--muted">Weekend</span>
35
+ <span class="hrl-badge hrl-badge--warn">H Holiday</span>
36
+ <span class="hrl-badge">N off On leave</span>
37
+ <span class="hrl-badge hrl-badge--muted">Weekly off</span>
38
+ <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--today">Today</span>
44
39
  </div>
40
+
41
+ <%# Names only — the leave type is the employee's own business. Leave keeps its label's "(first half)" note. %>
42
+ <% day = ->(d) { d.strftime("%a %-d %b") }
43
+ agenda = @holidays.values.flatten.map { |h| [ h.date, "#{day.(h.date)}: #{h.name} (holiday)" ] } +
44
+ @leaves.values.flatten.uniq.map { |r| [ [ r.start_date, first ].max, "#{[ r.start_date, r.end_date ].uniq.map(&day).join(' – ')}#{r.date_range_label[/ \(.+\)/]}: #{hr_display_name(r.user)}, on leave" ] } %>
45
+ <% if agenda.any? %>
46
+ <ul class="hrl-feed hrl-small hrl-mt">
47
+ <% agenda.sort_by(&:first).each do |_, line| %><li class="hrl-feed__item"><%= line %></li><% end %>
48
+ </ul>
49
+ <% else %>
50
+ <p class="hrl-muted hrl-small hrl-mt">No holidays or leave this month.</p>
51
+ <% end %>
45
52
  </section>
@@ -1,14 +1,14 @@
1
1
  <% content_for(:page_title) { "Comp-off" } %>
2
+ <% content_for(:back, "Leaves") %>
2
3
  <div class="hrl-page__head">
3
4
  <h1 class="hrl-page__title">My comp-off</h1>
4
5
  <div class="hrl-page__actions">
5
- <a class="hrl-btn" href="<%= hr_lite.leave_requests_path %>">My leaves</a>
6
6
  <a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_comp_off_request_path %>">Request comp-off</a>
7
7
  </div>
8
8
  </div>
9
9
 
10
10
  <section class="hrl-card">
11
- <p class="hrl-small hrl-muted" style="margin-top:0;">
11
+ <p class="hrl-small hrl-muted">
12
12
  Worked on a weekend or holiday? Request a comp-off — once an admin approves,
13
13
  <%= @comp_off_type ? "your #{@comp_off_type.name} balance is credited" : "a day is credited to your balance" %>
14
14
  and you spend it through the normal leave flow.
@@ -21,7 +21,7 @@
21
21
  <% @requests.each do |request| %>
22
22
  <tr>
23
23
  <td data-label="Day worked"><%= request.date_worked.strftime("%a, %d %b %Y") %></td>
24
- <td data-label="Credit" class="hrl-num"><%= request.credit_days.to_f %></td>
24
+ <td data-label="Credit" class="hrl-num"><%= hrl_days(request.credit_days) %></td>
25
25
  <td data-label="Status"><%= hrl_request_status_badge(request.status) %></td>
26
26
  <td data-label="Note" class="hrl-small hrl-muted"><%= request.decision_note.presence || "—" %></td>
27
27
  <td data-label="" class="<%= 'hrl-cell--void' unless request.pending? %>">
@@ -1,4 +1,5 @@
1
1
  <% content_for(:page_title) { "Request comp-off" } %>
2
+ <% content_for(:back, "Leaves") %>
2
3
  <div class="hrl-page__head">
3
4
  <h1 class="hrl-page__title">Request comp-off</h1>
4
5
  </div>
@@ -8,7 +9,7 @@
8
9
  <%= render "hr_lite/shared/form_errors", record: @request %>
9
10
  <div class="hrl-field">
10
11
  <%= f.label :date_worked, "Which day did you work?" %>
11
- <%= f.date_field :date_worked, max: Date.current %>
12
+ <%= f.date_field :date_worked, max: Date.current, required: true %>
12
13
  <p class="hrl-hint">Must be a weekend or holiday you actually worked — extra hours on a
13
14
  normal working day are not comp-off under the policy.</p>
14
15
  </div>
@@ -18,7 +19,7 @@
18
19
  </div>
19
20
  <div class="hrl-field">
20
21
  <%= f.label :reason, "What did you work on? (required)" %>
21
- <%= f.text_area :reason %>
22
+ <%= f.text_area :reason, required: true %>
22
23
  </div>
23
24
  <div class="hrl-form-actions">
24
25
  <%= f.submit "Send for approval", class: "hrl-btn hrl-btn--primary" %>
@@ -13,34 +13,6 @@
13
13
  </section>
14
14
  <% end %>
15
15
 
16
- <section class="hrl-card">
17
- <h2 class="hrl-card__title">Upload a document</h2>
18
- <%= form_with model: @document, url: hr_lite.documents_path, method: :post, local: true do |f| %>
19
- <%= render "hr_lite/shared/form_errors", record: @document %>
20
- <div class="hrl-field">
21
- <%= f.label :category %>
22
- <%= f.select :category,
23
- options_for_select(HrLite::Document::SENSITIVE_CATEGORIES.map { |c| [ c.humanize, c ] } +
24
- [ [ "Offer letter", "offer_letter" ], [ "Certificate", "certificate" ],
25
- [ "Other", "other" ] ], @document.category) %>
26
- </div>
27
- <div class="hrl-field"><%= f.label :title %><%= f.text_field :title %></div>
28
- <div class="hrl-field">
29
- <%= f.label :reference_number, "Document number (optional)" %>
30
- <%= f.text_field :reference_number %>
31
- </div>
32
- <div class="hrl-field">
33
- <%= f.label :expires_on, "Expires on (optional — we will remind you)" %>
34
- <%= f.date_field :expires_on %>
35
- </div>
36
- <div class="hrl-field">
37
- <%= f.label :file, "File (PDF or image, up to 10 MB)" %>
38
- <%= f.file_field :file %>
39
- </div>
40
- <div class="hrl-form-actions"><%= f.submit "Upload", class: "hrl-btn hrl-btn--primary" %></div>
41
- <% end %>
42
- </section>
43
-
44
16
  <section class="hrl-card">
45
17
  <h2 class="hrl-card__title">On file</h2>
46
18
  <% if @documents.empty? %>
@@ -54,7 +26,7 @@
54
26
  <tr>
55
27
  <td data-label="Document">
56
28
  <strong><%= doc.title %></strong>
57
- <div class="hrl-small hrl-muted"><%= doc.category.humanize %></div>
29
+ <div class="hrl-small hrl-muted"><%= doc.category.humanize.sub(/\APan\z/, "PAN") %></div>
58
30
  </td>
59
31
  <td data-label="Expires">
60
32
  <%= doc.expires_on ? doc.expires_on.strftime("%d %b %Y") : "—" %>
@@ -67,7 +39,7 @@
67
39
  </td>
68
40
  <td data-label="">
69
41
  <% if doc.file.attached? %>
70
- <a class="hrl-small" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
42
+ <a class="hrl-btn" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
71
43
  <% end %>
72
44
  <% unless doc.verified? %>
73
45
  <%= button_to "Remove", hr_lite.document_path(doc), method: :delete,
@@ -82,3 +54,33 @@
82
54
  </div>
83
55
  <% end %>
84
56
  </section>
57
+
58
+ <details class="hrl-card" <%= "open" if @documents.empty? || @document.errors.any? %>>
59
+ <summary class="hrl-btn hrl-btn--primary">Upload a document</summary>
60
+ <%= form_with model: @document, url: hr_lite.documents_path, method: :post, local: true, class: "hrl-mt" do |f| %>
61
+ <%= render "hr_lite/shared/form_errors", record: @document %>
62
+ <div class="hrl-field">
63
+ <%= f.label :category, id: "hrl-doc-category", for: nil %>
64
+ <div class="hrl-chiprow" role="radiogroup" aria-labelledby="hrl-doc-category">
65
+ <% (HrLite::Document::SENSITIVE_CATEGORIES.map { |c| [ c.humanize.sub(/\APan\z/, "PAN"), c ] } +
66
+ [ [ "Offer letter", "offer_letter" ], [ "Certificate", "certificate" ], [ "Other", "other" ] ]).each do |label, value| %>
67
+ <label><%= f.radio_button :category, value, required: true %><span><%= label %></span></label>
68
+ <% end %>
69
+ </div>
70
+ </div>
71
+ <div class="hrl-field"><%= f.label :title %><%= f.text_field :title, required: true %></div>
72
+ <div class="hrl-field">
73
+ <%= f.label :reference_number, "Document number (optional)" %>
74
+ <%= f.text_field :reference_number %>
75
+ </div>
76
+ <div class="hrl-field">
77
+ <%= f.label :expires_on, "Expires on (optional — we will remind you)" %>
78
+ <%= f.date_field :expires_on %>
79
+ </div>
80
+ <div class="hrl-field">
81
+ <%= f.label :file, "File (PDF or image, up to 10 MB)" %>
82
+ <%= f.file_field :file, required: true, accept: "application/pdf,image/*" %>
83
+ </div>
84
+ <div class="hrl-form-actions"><%= f.submit "Upload", class: "hrl-btn hrl-btn--primary" %></div>
85
+ <% end %>
86
+ </details>
@@ -9,8 +9,7 @@
9
9
  <dt>Employee code</dt><dd><%= @profile.employee_code %></dd>
10
10
  <dt>Designation</dt><dd><%= @profile.designation.presence || "—" %></dd>
11
11
  <dt>Reports to</dt>
12
- <dd><%= (boss = @profile.active_manager) ? "#{hr_display_name(boss)} (your L1)" : "—" %>
13
- · <a href="<%= hr_lite.org_path %>">Org chart</a></dd>
12
+ <dd><%= (boss = @profile.active_manager) ? "#{hr_display_name(boss)} (your L1)" : "—" %></dd>
14
13
  <dt>Department</dt><dd><%= @profile.department.presence || "—" %></dd>
15
14
  <dt>Joined</dt><dd><%= @profile.date_of_joining.strftime("%d %B %Y") %>
16
15
  <% if @profile.on_probation? %><span class="hrl-badge hrl-badge--warn">Probation</span><% end %></dd>
@@ -19,6 +18,7 @@
19
18
  <dt>PF UAN</dt><dd><%= @profile.masked_uan || "—" %></dd>
20
19
  <dt>Bank</dt><dd><%= [ @profile.bank_name, @profile.masked_account ].compact.join(" ").presence || "—" %></dd>
21
20
  </dl>
21
+ <a class="hrl-btn hrl-mt" href="<%= hr_lite.org_path %>">Org chart</a>
22
22
  <p class="hrl-small hrl-muted hrl-mt">Something wrong? Ask leadership to correct it — changes are audited.</p>
23
23
  <a class="hrl-btn hrl-mt" href="<%= hr_lite.resignation_path %>">Resignation</a>
24
24
  </section>
@@ -28,41 +28,23 @@
28
28
  <% if @expenses.empty? %>
29
29
  <p class="hrl-muted">No claims yet.</p>
30
30
  <% else %>
31
- <div class="hrl-table-wrap">
32
- <table class="hrl-table hrl-table--stack">
33
- <thead><tr><th>Spent</th><th>Category</th><th>Amount</th><th>Status</th><th></th></tr></thead>
34
- <tbody>
35
- <% @expenses.each do |expense| %>
36
- <tr>
37
- <td data-label="Spent"><%= expense.spent_on.strftime("%d %b %Y") %></td>
38
- <td data-label="Category">
39
- <%= expense.category.name %>
40
- <div class="hrl-small hrl-muted"><%= expense.description %></div>
41
- </td>
42
- <td data-label="Amount" class="hrl-num"><%= hrl_money(expense.amount) %></td>
43
- <td data-label="Status">
44
- <%= hrl_request_status_badge(expense.status) %>
45
- <% if expense.reimbursed_in %>
46
- <div class="hrl-small hrl-muted">
47
- with <%= expense.reimbursed_in.strftime("%B %Y") %> payroll
48
- </div>
49
- <% end %>
50
- <% if expense.decision_note.present? %>
51
- <div class="hrl-small hrl-muted"><%= expense.decision_note %></div>
52
- <% end %>
53
- </td>
54
- <td data-label="">
55
- <% if expense.draft? || expense.submitted? %>
56
- <%= button_to "Withdraw", hr_lite.cancel_expense_path(expense),
57
- class: "hrl-btn",
58
- form: { data: { turbo_confirm: "Withdraw this claim?" } } %>
59
- <% end %>
60
- </td>
61
- </tr>
62
- <% end %>
63
- </tbody>
64
- </table>
65
- </div>
31
+ <% @expenses.each do |expense| %>
32
+ <div class="hrl-listrow">
33
+ <span><%= expense.description %></span>
34
+ <span><%= hrl_money(expense.amount) %></span>
35
+ <span class="hrl-small hrl-muted">
36
+ <%= expense.spent_on.strftime("%d %b %Y") %> · <%= expense.category.name %>
37
+ <%= hrl_request_status_badge(expense.status) %>
38
+ <%= "· with #{expense.reimbursed_in.strftime('%B %Y')} payroll" if expense.reimbursed_in %>
39
+ <%= "· #{expense.decision_note}" if expense.decision_note.present? %>
40
+ </span>
41
+ <% if expense.draft? || expense.submitted? %>
42
+ <%= button_to "Withdraw", hr_lite.cancel_expense_path(expense),
43
+ class: "hrl-btn",
44
+ form: { data: { turbo_confirm: "Withdraw this claim?" } } %>
45
+ <% end %>
46
+ </div>
47
+ <% end %>
66
48
  <%= hrl_pagination %>
67
49
  <% end %>
68
50
  </section>
@@ -7,26 +7,31 @@
7
7
 
8
8
  <div class="hrl-field">
9
9
  <%= f.label :category_id, "What kind of expense" %>
10
- <%= f.select :category_id, @categories.map { |c| [ c.name, c.id ] },
11
- { include_blank: "Choose one" } %>
10
+ <% options = @categories.map do |c|
11
+ remaining = c.remaining_for(hr_current_user)
12
+ notes = [ ("receipt needed" if c.receipt_required),
13
+ ("#{hrl_money(remaining)} left this month" if remaining) ].compact
14
+ [ notes.any? ? "#{c.name} (#{notes.join(', ')})" : c.name, c.id ]
15
+ end %>
16
+ <%= f.select :category_id, options, { include_blank: "Choose one" }, { required: true } %>
12
17
  </div>
13
18
  <div class="hrl-field">
14
19
  <%= f.label :amount, "Amount (₹)" %>
15
- <%= f.text_field :amount, inputmode: "decimal" %>
20
+ <%= f.text_field :amount, inputmode: "decimal", required: true %>
16
21
  </div>
17
22
  <div class="hrl-field">
18
23
  <%= f.label :spent_on, "When you spent it" %>
19
- <%= f.date_field :spent_on, max: Date.current.to_s %>
24
+ <%= f.date_field :spent_on, max: Date.current.to_s, required: true %>
20
25
  </div>
21
26
  <div class="hrl-field">
22
27
  <%= f.label :description, "What it was for" %>
23
- <%= f.text_field :description %>
28
+ <%= f.text_field :description, required: true %>
24
29
  </div>
25
30
  <div class="hrl-field">
26
31
  <%= f.label :receipt, "Receipt (PDF or photo)" %>
27
32
  <%= f.file_field :receipt, accept: "application/pdf,image/*" %>
28
33
  <span class="hrl-small hrl-muted">
29
- Some categories need one — the form will say so if yours does.
34
+ Needed when the category says "receipt needed".
30
35
  </span>
31
36
  </div>
32
37
 
@@ -1,12 +1,15 @@
1
1
  <section class="hrl-card">
2
- <h2 class="hrl-card__title">Next holiday</h2>
3
- <% holiday = HrLite::Holiday.company_wide.upcoming.first %>
4
- <% if holiday %>
5
- <p><strong><%= holiday.name %></strong><br>
6
- <span class="hrl-muted hrl-small"><%= holiday.date.strftime("%A, %d %B %Y") %>
7
- (<%= (holiday.date - Date.current).to_i %> days away)</span></p>
2
+ <h2 class="hrl-card__title">Upcoming holidays</h2>
3
+ <% holidays = HrLite::Holiday.company_wide.upcoming.where(date: ..Date.current + 60).limit(3).to_a %>
4
+ <% if holidays.any? %>
5
+ <div class="hrl-feed">
6
+ <% holidays.each do |holiday| %>
7
+ <% days = (holiday.date - Date.current).to_i %>
8
+ <div class="hrl-feed__item"><div class="hrl-feed__body"><%= holiday.date.strftime("%a %-d %b") %> · <%= holiday.name %> · <%= days.zero? ? "today" : "in #{pluralize(days, "day")}" %></div></div>
9
+ <% end %>
10
+ </div>
8
11
  <% else %>
9
- <p class="hrl-muted hrl-small">No upcoming holidays on the calendar.</p>
12
+ <p class="hrl-muted hrl-small">No holidays in the next 2 months.</p>
10
13
  <% end %>
11
- <a class="hrl-small" href="<%= hr_lite.calendar_path %>">Company calendar</a>
14
+ <a class="hrl-card__link" href="<%= hr_lite.holidays_path %>">All holidays</a>
12
15
  </section>
@@ -1,10 +1,10 @@
1
1
  <% content_for(:page_title) { "Holidays" } %>
2
+ <% content_for(:back, "Calendar") %>
2
3
  <div class="hrl-page__head">
3
4
  <h1 class="hrl-page__title">Holidays <%= @year %></h1>
4
5
  <div class="hrl-page__actions">
5
6
  <a class="hrl-btn" href="<%= hr_lite.holidays_path(year: @year - 1) %>"><%= @year - 1 %></a>
6
7
  <a class="hrl-btn" href="<%= hr_lite.holidays_path(year: @year + 1) %>"><%= @year + 1 %></a>
7
- <a class="hrl-btn" href="<%= hr_lite.calendar_path %>">Calendar</a>
8
8
  </div>
9
9
  </div>
10
10
 
@@ -12,14 +12,14 @@
12
12
  <% if @holidays.any? %>
13
13
  <div class="hrl-table-wrap">
14
14
  <table class="hrl-table hrl-table--stack">
15
- <thead><tr><th>Date</th><th>Holiday</th><th></th></tr></thead>
15
+ <thead><tr><th>Date</th><th>Holiday</th></tr></thead>
16
16
  <tbody>
17
17
  <% @holidays.each do |holiday| %>
18
- <tr>
18
+ <% next_up = holiday.id == @next_holiday_id %>
19
+ <%= tag.tr class: ("hrl-muted" if holiday.date < Date.current) do %>
19
20
  <td data-label="Date"><%= holiday.date.strftime("%a, %d %b") %></td>
20
- <td data-label="Holiday"><%= holiday.name %></td>
21
- <td data-label=""><% if holiday.optional %><span class="hrl-badge hrl-badge--muted">Optional</span><% end %></td>
22
- </tr>
21
+ <td data-label="Holiday"><span class="hrl-row"><%= holiday.name %><% if next_up %> <span class="hrl-badge">Next</span><% end %><% if holiday.optional %> <span class="hrl-badge hrl-badge--muted">Optional</span><% end %></span></td>
22
+ <% end %>
23
23
  <% end %>
24
24
  </tbody>
25
25
  </table>
@@ -2,30 +2,40 @@
2
2
  <h1 class="hrl-page__title">Hello, <%= hr_display_name(hr_current_user) %></h1>
3
3
  </div>
4
4
 
5
+ <% if (@waiting + @queue).positive? %>
6
+ <section class="hrl-card">
7
+ <h2 class="hrl-card__title"><%= pluralize(@waiting + @queue, "request") %> waiting on you</h2>
8
+ <a class="hrl-btn hrl-btn--primary hrl-mt" href="<%= @waiting.positive? ? hr_lite.approvals_path : hr_lite.admin_leave_requests_path %>">Review requests</a>
9
+ </section>
10
+ <% end %>
11
+
5
12
  <div class="hrl-grid hrl-grid--2">
6
- <%= render "hr_lite/attendance/punch_card" %>
13
+ <%= render "hr_lite/attendance/punch_card", attention: @attention %>
7
14
  <%= render "hr_lite/leave_requests/balance_chips" %>
15
+
16
+ <% if @mine_pending.any? %>
17
+ <section class="hrl-card">
18
+ <h2 class="hrl-card__title">My requests</h2>
19
+ <% @mine_pending.each do |req| %>
20
+ <% title, detail, href = case req
21
+ when HrLite::LeaveRequest then [ req.leave_type.name, "#{req.date_range_label} · #{pluralize(hrl_days(req.days_count), 'day')}", hr_lite.leave_request_path(req) ]
22
+ when HrLite::RegularizationRequest then [ "Attendance fix", "#{req.date.strftime('%a %-d %b')} · #{req.times_label}", hr_lite.regularization_requests_path ]
23
+ else [ "Comp-off", "Worked #{req.date_worked.strftime('%a %-d %b')}", hr_lite.comp_off_requests_path ]
24
+ end %>
25
+ <a class="hrl-listrow" href="<%= href %>">
26
+ <span><%= title %></span>
27
+ <%= hrl_request_status_badge(req.status) %>
28
+ <span class="hrl-small hrl-muted"><%= detail %></span>
29
+ </a>
30
+ <% end %>
31
+ </section>
32
+ <% end %>
8
33
  <%= render "hr_lite/holidays/next_holiday" %>
9
34
 
10
35
  <section class="hrl-card">
11
- <h2 class="hrl-card__title">Out today</h2>
12
- <% if @out_today.any? %>
13
- <%# Name and dates only — the leave type (sick leave) is the employee's own business. %>
14
- <div class="hrl-feed">
15
- <% @out_today.first(10).each do |leave| %>
16
- <div class="hrl-feed__item">
17
- <div class="hrl-feed__body"><%= tag.strong hr_display_name(leave.user), title: "On leave: #{leave.date_range_label}" %>
18
- <div class="hrl-feed__meta"><%= leave.date_range_label %></div></div>
19
- </div>
20
- <% end %>
21
- </div>
22
- <% if @out_today.size > 10 %>
23
- <p class="hrl-muted hrl-small">+<%= @out_today.size - 10 %> more</p>
24
- <% end %>
25
- <% else %>
26
- <p class="hrl-muted hrl-small">Everyone's in today.</p>
27
- <% end %>
28
- <a class="hrl-small" href="<%= hr_lite.team_path %>">Team board</a>
36
+ <h2 class="hrl-card__title">On leave this week</h2>
37
+ <%= render "hr_lite/leave_requests/out_list", leaves: @out_week, empty: "Nobody is on leave this week." %>
38
+ <a class="hrl-card__link" href="<%= hr_lite.team_path %>">Team board</a>
29
39
  </section>
30
40
 
31
41
  <section class="hrl-card">
@@ -10,30 +10,13 @@
10
10
  <% if @requests.empty? %>
11
11
  <p class="hrl-muted">Nothing raised yet.</p>
12
12
  <% else %>
13
- <div class="hrl-table-wrap">
14
- <table class="hrl-table hrl-table--stack">
15
- <thead><tr><th>Request</th><th>Raised</th><th>Status</th><th></th></tr></thead>
16
- <tbody>
17
- <% @requests.each do |request| %>
18
- <tr>
19
- <td data-label="Request">
20
- <a href="<%= hr_lite.hr_request_path(request) %>"><%= request.subject %></a>
21
- <div class="hrl-small hrl-muted"><%= request.category_label %></div>
22
- </td>
23
- <td data-label="Raised"><%= request.created_at.to_date.strftime("%d %b %Y") %></td>
24
- <td data-label="Status"><%= hrl_request_status_badge(request.status) %></td>
25
- <td data-label="">
26
- <% if request.open? || request.in_progress? %>
27
- <%= button_to "Withdraw", hr_lite.cancel_hr_request_path(request),
28
- class: "hrl-btn",
29
- form: { data: { turbo_confirm: "Withdraw this request?" } } %>
30
- <% end %>
31
- </td>
32
- </tr>
33
- <% end %>
34
- </tbody>
35
- </table>
36
- </div>
13
+ <% @requests.each do |request| %>
14
+ <a class="hrl-listrow" href="<%= hr_lite.hr_request_path(request) %>">
15
+ <span><%= request.subject %></span>
16
+ <%= hrl_request_status_badge(request.status) %>
17
+ <span class="hrl-small hrl-muted"><%= request.category_label %> · <%= request.created_at.to_date.strftime("%d %b %Y") %></span>
18
+ </a>
19
+ <% end %>
37
20
  <%= hrl_pagination %>
38
21
  <% end %>
39
22
  </section>
@@ -6,14 +6,16 @@
6
6
  <%= render "hr_lite/shared/form_errors", record: @request %>
7
7
 
8
8
  <div class="hrl-field">
9
- <%= f.label :category, "What is this about" %>
10
- <%= f.select :category,
11
- HrLite::HrRequest::CATEGORIES.map { |c| [ c.humanize, c ] },
12
- { include_blank: "Choose one" } %>
9
+ <%= f.label :category, "What is this about", id: "hrl-request-category", for: nil %>
10
+ <div class="hrl-chiprow" role="radiogroup" aria-labelledby="hrl-request-category">
11
+ <% HrLite::HrRequest::CATEGORIES.each do |c| %>
12
+ <label><%= f.radio_button :category, c, required: true %><span><%= HrLite::HrRequest.category_label(c) %></span></label>
13
+ <% end %>
14
+ </div>
13
15
  </div>
14
16
  <div class="hrl-field">
15
17
  <%= f.label :subject, "In one line" %>
16
- <%= f.text_field :subject %>
18
+ <%= f.text_field :subject, required: true %>
17
19
  </div>
18
20
  <div class="hrl-field">
19
21
  <%= f.label :body, "Anything else HR should know" %>
@@ -12,6 +12,10 @@
12
12
  <% if @request.body.present? %>
13
13
  <div class="hrl-prose"><%= simple_format(@request.body) %></div>
14
14
  <% end %>
15
+ <% if @request.open? || @request.in_progress? %>
16
+ <%= button_to "Withdraw", hr_lite.cancel_hr_request_path(@request), class: "hrl-btn hrl-mt",
17
+ form: { data: { turbo_confirm: "Withdraw this request?" } } %>
18
+ <% end %>
15
19
  </section>
16
20
 
17
21
  <% if @request.resolution.present? %>
@@ -3,8 +3,9 @@
3
3
  <p class="hrl-page__sub">Appreciate teammates publicly. Mention people with @ — they get notified.</p>
4
4
  </div>
5
5
 
6
- <section class="hrl-card">
7
- <%= form_with model: @kudo, url: hr_lite.kudos_path, local: true do |f| %>
6
+ <details class="hrl-card" <%= "open" if @kudo.errors.any? %>>
7
+ <summary class="hrl-btn hrl-btn--primary hrl-btn--block">Give kudos</summary>
8
+ <%= form_with model: @kudo, url: hr_lite.kudos_path, local: true, class: "hrl-mt" do |f| %>
8
9
  <%= render "hr_lite/shared/form_errors", record: @kudo %>
9
10
  <div class="hrl-field">
10
11
  <%= f.label :message, "Say something nice" %>
@@ -24,7 +25,7 @@
24
25
  <%= f.submit "Post kudos", class: "hrl-btn hrl-btn--primary" %>
25
26
  </div>
26
27
  <% end %>
27
- </section>
28
+ </details>
28
29
 
29
30
  <section class="hrl-card">
30
31
  <% if @kudos.any? %>
@@ -1,19 +1,28 @@
1
1
  <% content_for(:page_title) { "Leave balances" } %>
2
+ <% content_for(:back, "Leaves") %>
2
3
  <div class="hrl-page__head">
3
4
  <h1 class="hrl-page__title">Leave balances <%= HrLite::LeaveYear.label(@year) %></h1>
5
+ <div class="hrl-page__actions">
6
+ <a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_leave_request_path %>">Apply</a>
7
+ </div>
4
8
  </div>
5
9
 
10
+ <% if @balances.any? %>
6
11
  <div class="hrl-grid hrl-grid--2">
7
12
  <% @balances.each do |balance| %>
8
13
  <section class="hrl-card">
9
14
  <h2 class="hrl-card__title"><%= balance.leave_type.name %></h2>
10
15
  <dl class="hrl-deflist">
11
- <dt>Entitled</dt><dd><%= balance.entitled.to_f %></dd>
12
- <dt>Used</dt><dd><%= balance.used.to_f %></dd>
13
- <dt>Available</dt><dd><strong><%= balance.available.to_f %></strong></dd>
14
- <% if balance.carried_forward.positive? %><dt>Carried forward</dt><dd><%= balance.carried_forward.to_f %></dd><% end %>
15
- <% if balance.adjustment.nonzero? %><dt>Adjustments</dt><dd><%= balance.adjustment.to_f %> <span class="hrl-muted hrl-small"><%= balance.adjustment_note %></span></dd><% end %>
16
+ <dt><%= balance.leave_type.comp_off ? "Credited" : "Entitled" %></dt><dd><%= hrl_days(balance.entitled) %></dd>
17
+ <dt>Used</dt><dd><%= hrl_days(balance.used) %></dd>
18
+ <dt>Available</dt><dd><strong><%= hrl_days(balance.available) %></strong></dd>
19
+ <% if balance.carried_forward.positive? %><dt>Carried forward</dt><dd><%= hrl_days(balance.carried_forward) %></dd><% end %>
20
+ <% if balance.adjustment.nonzero? %><dt>Adjustments</dt><dd><%= hrl_days(balance.adjustment) %> <span class="hrl-muted hrl-small"><%= balance.adjustment_note %></span></dd><% end %>
16
21
  </dl>
22
+ <a class="hrl-card__link" href="<%= hr_lite.new_leave_request_path(leave_type_id: balance.leave_type_id) %>">Apply</a>
17
23
  </section>
18
24
  <% end %>
19
25
  </div>
26
+ <% else %>
27
+ <p class="hrl-card hrl-card--empty">No leave types set up yet. Ask HR.</p>
28
+ <% end %>
@@ -5,12 +5,14 @@
5
5
  <div class="hrl-row">
6
6
  <% types.each do |type| %>
7
7
  <% balance = HrLite::LeaveBalance.for(hr_current_user, type, HrLite::LeaveYear.current_key) %>
8
- <span class="hrl-badge" style="background: <%= type.color %>1f; color: <%= type.color %>;">
9
- <%= type.code %> <%= balance.available.to_f %>/<%= balance.entitled.to_f %>
8
+ <span class="hrl-badge hrl-badge--type" style="--hrl-type: <%= type.color %>">
9
+ <%= type.name %> · <%= hrl_days(balance.available) %> of <%= hrl_days(balance.entitled) %> left
10
10
  </span>
11
11
  <% end %>
12
12
  </div>
13
- <a class="hrl-btn hrl-mt" href="<%= hr_lite.new_leave_request_path %>">Apply for leave</a>
13
+ <% unless local_assigns[:compact] %>
14
+ <a class="hrl-btn hrl-mt" href="<%= hr_lite.new_leave_request_path %>">Apply for leave</a>
15
+ <% end %>
14
16
  <% else %>
15
17
  <p class="hrl-muted hrl-small">No leave types configured yet.</p>
16
18
  <% end %>
@@ -0,0 +1,16 @@
1
+ <%# Name and dates only — the leave type (sick leave) is the employee's own business. %>
2
+ <% if leaves.any? %>
3
+ <div class="hrl-feed">
4
+ <% leaves.first(10).each do |leave| %>
5
+ <div class="hrl-feed__item">
6
+ <div class="hrl-feed__body"><%= tag.strong hr_display_name(leave.user), title: "On leave: #{leave.date_range_label}" %>
7
+ <div class="hrl-feed__meta"><%= leave.date_range_label %></div></div>
8
+ </div>
9
+ <% end %>
10
+ </div>
11
+ <% if leaves.size > 10 %>
12
+ <p class="hrl-muted hrl-small">+<%= leaves.size - 10 %> more</p>
13
+ <% end %>
14
+ <% else %>
15
+ <p class="hrl-muted hrl-small"><%= empty %></p>
16
+ <% end %>