hr_lite 0.21.3 → 0.22.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +67 -0
- data/README.md +54 -2
- data/app/assets/javascripts/hr_lite/confirm.js +18 -3
- data/app/assets/javascripts/hr_lite/geo_punch.js +1 -1
- data/app/assets/javascripts/hr_lite/nav.js +17 -0
- data/app/assets/javascripts/hr_lite/select_filter.js +46 -0
- data/app/assets/stylesheets/hr_lite/hr_lite.css +217 -79
- data/app/controllers/hr_lite/admin/assets_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/attendances_controller.rb +31 -0
- data/app/controllers/hr_lite/approvals_controller.rb +1 -12
- data/app/controllers/hr_lite/attendance_controller.rb +7 -0
- data/app/controllers/hr_lite/holidays_controller.rb +1 -0
- data/app/controllers/hr_lite/home_controller.rb +40 -3
- data/app/controllers/hr_lite/hr_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/leave_requests_controller.rb +13 -2
- data/app/controllers/hr_lite/regularization_requests_controller.rb +10 -2
- data/app/controllers/hr_lite/tax_declarations_controller.rb +4 -0
- data/app/helpers/hr_lite/application_helper.rb +43 -30
- data/app/jobs/hr_lite/attendance_close_job.rb +6 -1
- data/app/models/hr_lite/approval.rb +5 -0
- data/app/models/hr_lite/attendance_record.rb +26 -0
- data/app/models/hr_lite/employee_profile.rb +1 -0
- data/app/models/hr_lite/hr_request.rb +2 -1
- data/app/models/hr_lite/office_location.rb +12 -0
- data/app/models/hr_lite/regularization_request.rb +10 -4
- data/app/services/hr_lite/attendance_puncher.rb +8 -3
- data/app/views/hr_lite/admin/assets/index.html.erb +3 -2
- data/app/views/hr_lite/admin/assets/new.html.erb +6 -2
- data/app/views/hr_lite/admin/attendances/index.html.erb +37 -6
- data/app/views/hr_lite/admin/attendances/show.html.erb +11 -7
- data/app/views/hr_lite/admin/attendances/week.html.erb +42 -0
- data/app/views/hr_lite/admin/checklists/index.html.erb +1 -1
- data/app/views/hr_lite/admin/comp_off_requests/index.html.erb +2 -2
- data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +2 -2
- data/app/views/hr_lite/admin/documents/index.html.erb +1 -1
- data/app/views/hr_lite/admin/employees/_form.html.erb +15 -7
- data/app/views/hr_lite/admin/employees/show.html.erb +5 -5
- data/app/views/hr_lite/admin/expenses/index.html.erb +2 -2
- data/app/views/hr_lite/admin/leave_balances/index.html.erb +3 -3
- data/app/views/hr_lite/admin/leave_requests/index.html.erb +2 -2
- data/app/views/hr_lite/admin/leave_requests/show.html.erb +4 -6
- data/app/views/hr_lite/admin/loans/new.html.erb +2 -1
- data/app/views/hr_lite/admin/overview/index.html.erb +3 -3
- data/app/views/hr_lite/admin/regularization_requests/index.html.erb +1 -1
- data/app/views/hr_lite/admin/shared/_approvals_tabs.html.erb +6 -5
- data/app/views/hr_lite/appraisals/show.html.erb +1 -1
- data/app/views/hr_lite/approvals/index.html.erb +1 -1
- data/app/views/hr_lite/attendance/_month_grid.html.erb +21 -26
- data/app/views/hr_lite/attendance/_punch_card.html.erb +29 -7
- data/app/views/hr_lite/attendance/show.html.erb +36 -7
- data/app/views/hr_lite/benefits/index.html.erb +1 -1
- data/app/views/hr_lite/calendar/show.html.erb +29 -22
- data/app/views/hr_lite/comp_off_requests/index.html.erb +3 -3
- data/app/views/hr_lite/comp_off_requests/new.html.erb +3 -2
- data/app/views/hr_lite/documents/index.html.erb +32 -30
- data/app/views/hr_lite/employee_profiles/show.html.erb +2 -2
- data/app/views/hr_lite/expenses/index.html.erb +17 -35
- data/app/views/hr_lite/expenses/new.html.erb +11 -6
- data/app/views/hr_lite/holidays/_next_holiday.html.erb +11 -8
- data/app/views/hr_lite/holidays/index.html.erb +6 -6
- data/app/views/hr_lite/home/index.html.erb +29 -19
- data/app/views/hr_lite/hr_requests/index.html.erb +7 -24
- data/app/views/hr_lite/hr_requests/new.html.erb +7 -5
- data/app/views/hr_lite/hr_requests/show.html.erb +4 -0
- data/app/views/hr_lite/kudos/index.html.erb +4 -3
- data/app/views/hr_lite/leave_balances/index.html.erb +14 -5
- data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +5 -3
- data/app/views/hr_lite/leave_requests/_out_list.html.erb +16 -0
- data/app/views/hr_lite/leave_requests/index.html.erb +8 -21
- data/app/views/hr_lite/leave_requests/new.html.erb +22 -9
- data/app/views/hr_lite/leave_requests/show.html.erb +3 -5
- data/app/views/hr_lite/loans/index.html.erb +18 -20
- data/app/views/hr_lite/policies/index.html.erb +1 -1
- data/app/views/hr_lite/regularization_requests/index.html.erb +2 -2
- data/app/views/hr_lite/regularization_requests/new.html.erb +11 -8
- data/app/views/hr_lite/resignations/show.html.erb +1 -1
- data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +9 -3
- data/app/views/hr_lite/salary_slips/index.html.erb +48 -44
- data/app/views/hr_lite/shared/_bell.html.erb +1 -1
- data/app/views/hr_lite/shared/_form_errors.html.erb +1 -1
- data/app/views/hr_lite/tax_declarations/show.html.erb +24 -19
- data/app/views/hr_lite/team/show.html.erb +2 -2
- data/app/views/layouts/hr_lite/application.html.erb +24 -8
- data/config/routes.rb +3 -1
- data/lib/hr_lite/configuration.rb +16 -2
- data/lib/hr_lite/notifications.rb +1 -0
- data/lib/hr_lite/version.rb +1 -1
- metadata +4 -1
|
@@ -3,24 +3,68 @@
|
|
|
3
3
|
overriding the variables below (see README), never by editing this file. */
|
|
4
4
|
|
|
5
5
|
:root {
|
|
6
|
+
/* Type. rem so OS text scaling works; nothing renders below 12px. */
|
|
6
7
|
--hrl-font: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
8
|
+
--hrl-font-head: var(--hrl-font);
|
|
9
|
+
--hrl-t-micro: .75rem;
|
|
10
|
+
--hrl-t-small: .875rem;
|
|
11
|
+
--hrl-t-body: 1rem;
|
|
12
|
+
--hrl-t-lead: 1.125rem;
|
|
13
|
+
--hrl-t-title: 1.25rem;
|
|
14
|
+
--hrl-t-display: 1.5rem;
|
|
15
|
+
--hrl-lh-tight: 1.25;
|
|
16
|
+
--hrl-lh-body: 1.5;
|
|
17
|
+
/* Colour roles. */
|
|
7
18
|
--hrl-accent: #2563eb;
|
|
8
19
|
--hrl-accent-ink: #ffffff;
|
|
9
20
|
--hrl-ink: #111827;
|
|
10
|
-
--hrl-
|
|
21
|
+
--hrl-ink-2: #374151;
|
|
22
|
+
--hrl-muted: #4b5563;
|
|
11
23
|
--hrl-bg: #f4f5f7;
|
|
12
24
|
--hrl-card: #ffffff;
|
|
13
25
|
--hrl-line: #e5e7eb;
|
|
26
|
+
--hrl-field: #6b7280;
|
|
14
27
|
--hrl-ok: #047857;
|
|
15
28
|
--hrl-ok-bg: #d1fae5;
|
|
16
29
|
--hrl-warn: #b45309;
|
|
17
30
|
--hrl-warn-bg: #fef3c7;
|
|
18
31
|
--hrl-bad: #b91c1c;
|
|
19
32
|
--hrl-bad-bg: #fee2e2;
|
|
20
|
-
--hrl-info-bg: #
|
|
21
|
-
--hrl-
|
|
22
|
-
|
|
33
|
+
--hrl-info-bg: #eff6ff;
|
|
34
|
+
--hrl-scrim: rgba(15, 23, 42, .32);
|
|
35
|
+
/* Spacing. */
|
|
36
|
+
--hrl-s-1: 4px;
|
|
37
|
+
--hrl-s-2: 8px;
|
|
38
|
+
--hrl-s-3: 12px;
|
|
39
|
+
--hrl-s-4: 16px;
|
|
40
|
+
--hrl-s-5: 20px;
|
|
41
|
+
--hrl-s-6: 24px;
|
|
42
|
+
--hrl-s-8: 32px;
|
|
43
|
+
--hrl-s-10: 40px;
|
|
44
|
+
--hrl-s-12: 48px;
|
|
45
|
+
--hrl-gutter: .85rem;
|
|
46
|
+
--hrl-target: 44px;
|
|
47
|
+
--hrl-gap-target: 8px;
|
|
48
|
+
/* Radii. */
|
|
49
|
+
--hrl-radius-sm: 12px;
|
|
50
|
+
--hrl-radius: 16px;
|
|
51
|
+
--hrl-radius-lg: 20px;
|
|
52
|
+
--hrl-radius-pill: 9999px;
|
|
53
|
+
/* Elevation. */
|
|
23
54
|
--hrl-shadow: 0 1px 3px rgba(17, 24, 39, .07);
|
|
55
|
+
--hrl-shadow-md: 0 6px 18px rgba(15, 23, 42, .08);
|
|
56
|
+
--hrl-shadow-lg: 0 18px 40px rgba(15, 23, 42, .14);
|
|
57
|
+
--hrl-shadow-sheet: 0 -6px 18px rgba(15, 23, 42, .08);
|
|
58
|
+
/* Focus, motion, layering. */
|
|
59
|
+
--hrl-focus: var(--hrl-accent);
|
|
60
|
+
--hrl-focus-w: 2px;
|
|
61
|
+
--hrl-focus-offset: 2px;
|
|
62
|
+
--hrl-ease: cubic-bezier(.2, .6, .2, 1);
|
|
63
|
+
--hrl-dur-fast: 120ms;
|
|
64
|
+
--hrl-dur: 220ms;
|
|
65
|
+
--hrl-z-sticky: 20;
|
|
66
|
+
--hrl-z-menu: 30;
|
|
67
|
+
--hrl-z-sheet: 40;
|
|
24
68
|
}
|
|
25
69
|
|
|
26
70
|
* { box-sizing: border-box; }
|
|
@@ -29,6 +73,7 @@
|
|
|
29
73
|
font-family: var(--hrl-font);
|
|
30
74
|
color: var(--hrl-ink);
|
|
31
75
|
background: var(--hrl-bg);
|
|
76
|
+
line-height: var(--hrl-lh-body);
|
|
32
77
|
-webkit-font-smoothing: antialiased;
|
|
33
78
|
overflow-x: hidden;
|
|
34
79
|
}
|
|
@@ -36,6 +81,24 @@
|
|
|
36
81
|
(.hrl-btn--primary, .hrl-nav__link, ...) can set its own link colour —
|
|
37
82
|
a plain `.hrl-body a` would out-rank them and paint accent-on-accent. */
|
|
38
83
|
:where(.hrl-body) a { color: var(--hrl-accent); text-decoration: none; }
|
|
84
|
+
/* Keyboard focus: one ring everywhere (the second selector is a date input's picker button, focused inside its shadow);
|
|
85
|
+
scroll padding keeps it clear of the sticky app bar and fixed tab bar. */
|
|
86
|
+
:where(.hrl-body) :focus-visible,
|
|
87
|
+
:where(.hrl-body) input:focus-within:not(:focus) { outline: var(--hrl-focus-w) solid var(--hrl-focus); outline-offset: var(--hrl-focus-offset); }
|
|
88
|
+
html:has(.hrl-body) { scroll-padding-top: 4rem; scroll-padding-bottom: calc(4.4rem + env(safe-area-inset-bottom)); }
|
|
89
|
+
.hrl-sr-only, .hrl-skip:not(:focus) {
|
|
90
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
91
|
+
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
92
|
+
}
|
|
93
|
+
.hrl-skip:focus {
|
|
94
|
+
position: fixed; top: var(--hrl-s-2); left: var(--hrl-s-2); z-index: var(--hrl-z-sheet);
|
|
95
|
+
display: inline-flex; align-items: center; min-height: var(--hrl-target); padding: 0 var(--hrl-s-4);
|
|
96
|
+
background: var(--hrl-card); color: var(--hrl-accent); font-weight: 600;
|
|
97
|
+
border-radius: var(--hrl-radius-sm); box-shadow: var(--hrl-shadow-md);
|
|
98
|
+
}
|
|
99
|
+
@media (prefers-reduced-motion: reduce) {
|
|
100
|
+
.hrl-body *, .hrl-body *::before, .hrl-body *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
|
|
101
|
+
}
|
|
39
102
|
.hrl-shell { display: flex; min-height: 100vh; }
|
|
40
103
|
.hrl-content { flex: 1; min-width: 0; }
|
|
41
104
|
.hrl-main {
|
|
@@ -49,8 +112,8 @@
|
|
|
49
112
|
/* Left rail (Keka-style) — hidden on phones, fixed column on >=768px */
|
|
50
113
|
.hrl-side { display: none; }
|
|
51
114
|
.hrl-nav__link {
|
|
52
|
-
color: var(--hrl-muted); font-size:
|
|
53
|
-
border-radius: var(--hrl-radius-sm); min-height:
|
|
115
|
+
color: var(--hrl-muted); font-size: var(--hrl-t-small); padding: .5rem .6rem;
|
|
116
|
+
border-radius: var(--hrl-radius-sm); min-height: var(--hrl-target);
|
|
54
117
|
display: inline-flex; align-items: center;
|
|
55
118
|
}
|
|
56
119
|
.hrl-nav__link--active { color: var(--hrl-accent); background: var(--hrl-info-bg); font-weight: 600; }
|
|
@@ -59,50 +122,59 @@
|
|
|
59
122
|
.hrl-appbar--mobile {
|
|
60
123
|
background: var(--hrl-card);
|
|
61
124
|
border-bottom: 1px solid var(--hrl-line);
|
|
62
|
-
padding: calc(
|
|
63
|
-
position: sticky; top: 0; z-index:
|
|
125
|
+
padding: calc(var(--hrl-s-2) + env(safe-area-inset-top)) var(--hrl-gutter) var(--hrl-s-2);
|
|
126
|
+
position: sticky; top: 0; z-index: var(--hrl-z-sticky);
|
|
64
127
|
display: flex; align-items: center; gap: .75rem;
|
|
65
128
|
}
|
|
66
|
-
.hrl-appbar__brand { font-weight: 700; font-size:
|
|
67
|
-
.hrl-appbar__logo { height: 28px; width: auto;
|
|
68
|
-
.hrl-appbar__user { margin-left: auto; color: var(--hrl-muted); font-size:
|
|
129
|
+
.hrl-appbar__brand { font-weight: 700; font-size: var(--hrl-t-body); color: var(--hrl-ink); line-height: var(--hrl-target); flex: 0 1 auto; min-width: var(--hrl-target); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
130
|
+
.hrl-appbar__logo { height: 28px; width: auto; vertical-align: middle; }
|
|
131
|
+
.hrl-appbar__user { margin-left: auto; color: var(--hrl-muted); font-size: var(--hrl-t-small); line-height: var(--hrl-target); white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
69
132
|
|
|
70
133
|
/* Bottom tab bar (phone) */
|
|
71
134
|
.hrl-tabbar {
|
|
72
|
-
position: fixed; bottom: 0; left: 0; right: 0; z-index:
|
|
73
|
-
display: flex; justify-content: space-around;
|
|
135
|
+
position: fixed; bottom: 0; left: 0; right: 0; z-index: var(--hrl-z-sticky);
|
|
136
|
+
display: flex; justify-content: space-around; gap: var(--hrl-gap-target);
|
|
74
137
|
background: var(--hrl-card); border-top: 1px solid var(--hrl-line);
|
|
75
138
|
padding: .25rem .25rem calc(.25rem + env(safe-area-inset-bottom));
|
|
76
139
|
}
|
|
77
|
-
.hrl-tabbar .hrl-nav__link { flex: 1; justify-content: center; font-size:
|
|
140
|
+
.hrl-tabbar .hrl-nav__link { flex: 1 1 auto; min-width: var(--hrl-target); padding-inline: var(--hrl-s-1); justify-content: center; font-size: var(--hrl-t-micro); font-weight: 600; }
|
|
78
141
|
/* "More" sheet — everything five tabs cannot hold. Pure <details>: the
|
|
79
142
|
engine ships no Turbo or Stimulus and this must not need any. */
|
|
80
|
-
.hrl-tabbar__more { flex: 1; display: flex; }
|
|
143
|
+
.hrl-tabbar__more { flex: 1 1 auto; display: flex; }
|
|
81
144
|
.hrl-tabbar__more > summary {
|
|
82
145
|
flex: 1; justify-content: center; list-style: none; cursor: pointer;
|
|
83
146
|
}
|
|
84
147
|
.hrl-tabbar__more > summary::-webkit-details-marker { display: none; }
|
|
85
|
-
.hrl-tabbar__more[open] > summary
|
|
148
|
+
.hrl-tabbar__more[open] > summary,
|
|
149
|
+
.hrl-tabbar__more > summary.hrl-nav__link--active { color: var(--hrl-accent); font-weight: 600; }
|
|
86
150
|
.hrl-sheet {
|
|
87
151
|
position: fixed; left: 0; right: 0;
|
|
88
152
|
bottom: calc(3.3rem + env(safe-area-inset-bottom));
|
|
89
|
-
max-height:
|
|
90
|
-
display:
|
|
153
|
+
max-height: calc(100vh - 8rem - env(safe-area-inset-top) - env(safe-area-inset-bottom)); overflow-y: auto;
|
|
154
|
+
display: grid; grid-template-columns: 1fr 1fr; gap: 2px var(--hrl-gap-target);
|
|
91
155
|
background: var(--hrl-card); border-top: 1px solid var(--hrl-line);
|
|
156
|
+
border-radius: var(--hrl-radius-lg) var(--hrl-radius-lg) 0 0;
|
|
92
157
|
padding: .6rem .85rem;
|
|
93
|
-
box-shadow:
|
|
158
|
+
box-shadow: var(--hrl-shadow-sheet);
|
|
94
159
|
}
|
|
95
160
|
/* Left-aligned like the desktop rail — the tab-bar rule centres its own
|
|
96
161
|
links and would otherwise apply here too. */
|
|
97
162
|
.hrl-sheet .hrl-nav__link {
|
|
98
163
|
display: flex; justify-content: flex-start;
|
|
99
|
-
min-height:
|
|
164
|
+
min-height: var(--hrl-target); font-size: var(--hrl-t-body); padding: .5rem .6rem;
|
|
100
165
|
}
|
|
101
166
|
.hrl-sheet .hrl-side__group {
|
|
102
|
-
color: var(--hrl-muted); font-size:
|
|
167
|
+
color: var(--hrl-muted); font-size: var(--hrl-t-micro); font-weight: 700;
|
|
103
168
|
text-transform: uppercase; letter-spacing: .06em;
|
|
104
169
|
margin: .7rem 0 .15rem; padding: 0 .6rem;
|
|
105
170
|
}
|
|
171
|
+
.hrl-sheet .hrl-side__group, .hrl-sheet .hrl-side__back { grid-column: 1 / -1; }
|
|
172
|
+
@media (min-width: 481px) { .hrl-sheet { grid-template-columns: 1fr; } }
|
|
173
|
+
/* Scrim over the page while the sheet is open; tapping it closes the sheet (nav.js). */
|
|
174
|
+
.hrl-tabbar__more[open]::before {
|
|
175
|
+
content: ""; position: fixed; inset: 0 0 calc(3.3rem + env(safe-area-inset-bottom));
|
|
176
|
+
background: var(--hrl-scrim); z-index: -1;
|
|
177
|
+
}
|
|
106
178
|
|
|
107
179
|
@media (min-width: 768px) {
|
|
108
180
|
.hrl-tabbar { display: none; }
|
|
@@ -116,13 +188,13 @@
|
|
|
116
188
|
position: sticky; top: 0; height: 100vh; overflow-y: auto;
|
|
117
189
|
padding: 1rem .75rem;
|
|
118
190
|
}
|
|
119
|
-
.hrl-side__brand { display:
|
|
191
|
+
.hrl-side__brand { display: flex; align-items: center; min-height: var(--hrl-target); padding: .25rem .5rem 1rem; }
|
|
120
192
|
.hrl-side__logo { max-height: 40px; max-width: 100%; display: block; }
|
|
121
|
-
.hrl-side__brandname { font-weight:
|
|
193
|
+
.hrl-side__brandname { font-family: var(--hrl-font-head); font-weight: 700; font-size: var(--hrl-t-lead); color: var(--hrl-ink); }
|
|
122
194
|
.hrl-side__nav { display: flex; flex-direction: column; gap: 2px; }
|
|
123
|
-
.hrl-side__nav .hrl-nav__link { display: flex;
|
|
195
|
+
.hrl-side__nav .hrl-nav__link { display: flex; padding: .45rem .6rem; }
|
|
124
196
|
.hrl-side__group {
|
|
125
|
-
margin: .9rem .5rem .25rem; font-size:
|
|
197
|
+
margin: .9rem .5rem .25rem; font-size: var(--hrl-t-micro); font-weight: 700;
|
|
126
198
|
letter-spacing: .08em; text-transform: uppercase; color: var(--hrl-muted);
|
|
127
199
|
}
|
|
128
200
|
.hrl-side__group:first-child { margin-top: 0; }
|
|
@@ -130,20 +202,27 @@
|
|
|
130
202
|
margin-top: auto; padding: .75rem .5rem 0; border-top: 1px solid var(--hrl-line);
|
|
131
203
|
display: flex; flex-direction: column; gap: .35rem;
|
|
132
204
|
}
|
|
133
|
-
.hrl-side__user { font-size:
|
|
134
|
-
.hrl-side__back { font-size:
|
|
205
|
+
.hrl-side__user { font-size: var(--hrl-t-small); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
206
|
+
.hrl-side__back { display: flex; align-items: center; min-height: var(--hrl-target); font-size: var(--hrl-t-small); }
|
|
135
207
|
}
|
|
136
208
|
|
|
137
209
|
/* Flash */
|
|
138
|
-
.hrl-flash { padding: .7rem .9rem; border-radius: var(--hrl-radius-sm); margin-bottom: .85rem; font-size:
|
|
210
|
+
.hrl-flash { padding: .7rem .9rem; border-radius: var(--hrl-radius-sm); margin-bottom: .85rem; font-size: var(--hrl-t-body); overflow-wrap: anywhere; }
|
|
139
211
|
.hrl-flash--ok { background: var(--hrl-ok-bg); color: var(--hrl-ok); }
|
|
140
212
|
.hrl-flash--warn { background: var(--hrl-warn-bg); color: var(--hrl-warn); }
|
|
141
213
|
|
|
142
214
|
/* Page scaffold */
|
|
143
215
|
.hrl-page__head { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin: .25rem 0 .9rem; }
|
|
144
|
-
.hrl-page__title { font-size:
|
|
145
|
-
.hrl-
|
|
146
|
-
.hrl-
|
|
216
|
+
.hrl-page__title { font-family: var(--hrl-font-head); font-size: var(--hrl-t-title); font-weight: 700; line-height: var(--hrl-lh-tight); margin: 0; overflow-wrap: anywhere; }
|
|
217
|
+
@media (min-width: 768px) { .hrl-page__title { font-size: var(--hrl-t-display); } }
|
|
218
|
+
.hrl-page__sub { color: var(--hrl-muted); font-size: var(--hrl-t-small); margin: .15rem 0 0; width: 100%; }
|
|
219
|
+
.hrl-page__actions { width: 100%; display: flex; flex-wrap: wrap; gap: .5rem; }
|
|
220
|
+
.hrl-page__actions .hrl-btn { flex: 1 1 auto; }
|
|
221
|
+
.hrl-page__actions .hrl-btn[aria-label] { flex: 0 0 auto; } /* icon-only pager arrows keep their width */
|
|
222
|
+
@media (min-width: 481px) {
|
|
223
|
+
.hrl-page__actions { width: auto; margin-left: auto; }
|
|
224
|
+
.hrl-page__actions .hrl-btn { flex: 0 1 auto; }
|
|
225
|
+
}
|
|
147
226
|
|
|
148
227
|
/* Cards */
|
|
149
228
|
.hrl-card {
|
|
@@ -151,124 +230,163 @@
|
|
|
151
230
|
border-radius: var(--hrl-radius); box-shadow: var(--hrl-shadow);
|
|
152
231
|
padding: .95rem; margin-bottom: .85rem;
|
|
153
232
|
}
|
|
154
|
-
.hrl-card__title { font-weight:
|
|
233
|
+
.hrl-card__title { font-family: var(--hrl-font-head); font-weight: 700; font-size: var(--hrl-t-lead); line-height: var(--hrl-lh-tight); margin: 0 0 .6rem; }
|
|
155
234
|
.hrl-card--empty { text-align: center; color: var(--hrl-muted); padding: 1.8rem 1rem; }
|
|
156
|
-
.hrl-
|
|
235
|
+
.hrl-card--warn { background: var(--hrl-warn-bg); border-color: transparent; }
|
|
236
|
+
.hrl-card > p:first-child { margin-top: 0; }
|
|
237
|
+
.hrl-card__link, .hrl-backlink { display: inline-flex; gap: var(--hrl-s-1); align-items: center; min-height: var(--hrl-target); min-width: var(--hrl-target); font-size: var(--hrl-t-small); font-weight: 600; }
|
|
238
|
+
.hrl-listrow { display: grid; grid-template-columns: 1fr auto; align-items: start; gap: var(--hrl-s-1) var(--hrl-s-3); padding: var(--hrl-s-3) 0; border-bottom: 1px solid var(--hrl-line); min-height: var(--hrl-target); color: var(--hrl-ink); overflow-wrap: anywhere; }
|
|
239
|
+
.hrl-listrow:last-child { border-bottom: 0; }
|
|
240
|
+
.hrl-listrow > :first-child { display: inline-flex; align-items: center; gap: var(--hrl-s-1); line-height: var(--hrl-lh-tight); }
|
|
241
|
+
/* The punch status stays in the accessibility tree (no `hidden`) so its first message is announced; empty, it takes no space. */
|
|
242
|
+
[data-hrl-geo-status]:empty { margin: 0; }
|
|
243
|
+
.hrl-grid { display: grid; gap: .85rem; grid-template-columns: 1fr; margin-bottom: .85rem; }
|
|
157
244
|
.hrl-grid > * { min-width: 0; }
|
|
158
|
-
|
|
245
|
+
.hrl-grid > .hrl-card { margin-bottom: 0; }
|
|
246
|
+
@media (min-width: 1024px) { .hrl-grid--2 { grid-template-columns: 1fr 1fr; } }
|
|
159
247
|
|
|
160
248
|
/* KPI tiles */
|
|
161
249
|
.hrl-kpis { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; margin-bottom: .85rem; }
|
|
162
250
|
@media (min-width: 1100px) { .hrl-kpis { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
|
163
251
|
.hrl-kpi { background: var(--hrl-card); border: 1px solid var(--hrl-line); border-radius: var(--hrl-radius); padding: .7rem .8rem; }
|
|
164
|
-
.hrl-kpi__value { font-size:
|
|
165
|
-
.hrl-kpi__label { color: var(--hrl-muted); font-size:
|
|
252
|
+
.hrl-kpi__value { font-size: var(--hrl-t-display); font-weight: 700; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
253
|
+
.hrl-kpi__label { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
254
|
+
/* Rupee tiles: a lakh amount fits a half-width 320px tile without splitting its digits. */
|
|
255
|
+
.hrl-kpis--money .hrl-kpi__value { font-size: var(--hrl-t-lead); }
|
|
256
|
+
@media (min-width: 481px) { .hrl-kpis--money .hrl-kpi__value { font-size: var(--hrl-t-display); } }
|
|
166
257
|
|
|
167
258
|
/* Buttons */
|
|
168
259
|
.hrl-btn {
|
|
169
260
|
display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
|
|
170
|
-
min-height:
|
|
261
|
+
min-height: var(--hrl-target); padding: .55rem 1rem; border-radius: var(--hrl-radius-sm);
|
|
171
262
|
border: 1px solid var(--hrl-line); background: var(--hrl-card); color: var(--hrl-ink);
|
|
172
|
-
font-size:
|
|
263
|
+
font-size: var(--hrl-t-body); font-weight: 600; cursor: pointer; text-decoration: none;
|
|
264
|
+
max-width: 100%; overflow-wrap: anywhere; text-align: center;
|
|
173
265
|
}
|
|
174
266
|
.hrl-btn--primary { background: var(--hrl-accent); border-color: var(--hrl-accent); color: var(--hrl-accent-ink); }
|
|
175
267
|
.hrl-btn--danger { color: var(--hrl-bad); border-color: var(--hrl-bad-bg); }
|
|
176
268
|
.hrl-btn--block { width: 100%; }
|
|
177
|
-
.hrl-btn--big { min-height: 58px; font-size:
|
|
269
|
+
.hrl-btn--big { min-height: 58px; font-size: var(--hrl-t-lead); }
|
|
178
270
|
.hrl-btn[disabled] { opacity: .55; cursor: default; }
|
|
271
|
+
summary.hrl-btn { list-style: none; }
|
|
272
|
+
summary.hrl-btn::-webkit-details-marker { display: none; } /* Safari before 18.4 draws it whatever the display */
|
|
179
273
|
|
|
180
274
|
/* Badges & chips */
|
|
181
275
|
.hrl-badge {
|
|
182
|
-
display: inline-
|
|
183
|
-
font-size:
|
|
184
|
-
border-radius:
|
|
185
|
-
white-space: nowrap;
|
|
276
|
+
display: inline-block;
|
|
277
|
+
font-size: var(--hrl-t-micro); font-weight: 600; line-height: var(--hrl-lh-tight); padding: .18rem .55rem;
|
|
278
|
+
border-radius: var(--hrl-radius-pill); background: var(--hrl-info-bg); color: var(--hrl-accent);
|
|
279
|
+
white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
|
|
186
280
|
}
|
|
187
281
|
.hrl-badge--ok { background: var(--hrl-ok-bg); color: var(--hrl-ok); }
|
|
188
282
|
.hrl-badge--warn { background: var(--hrl-warn-bg); color: var(--hrl-warn); }
|
|
189
283
|
.hrl-badge--bad { background: var(--hrl-bad-bg); color: var(--hrl-bad); }
|
|
190
|
-
.hrl-badge--muted { background: var(--hrl-bg); color: var(--hrl-
|
|
191
|
-
|
|
284
|
+
.hrl-badge--muted { background: var(--hrl-bg); color: var(--hrl-ink-2); }
|
|
285
|
+
/* Leave-type badge: ink text on grey; the HR-chosen colour (inline --hrl-type) is only the dot. */
|
|
286
|
+
.hrl-badge--type { background: var(--hrl-bg); color: var(--hrl-ink); }
|
|
287
|
+
.hrl-badge--type::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: .3rem; border-radius: var(--hrl-radius-pill); background: var(--hrl-type); }
|
|
288
|
+
.hrl-mention { color: var(--hrl-accent); font-weight: 700; }
|
|
192
289
|
|
|
193
290
|
/* Tables (stack to label/value rows on phones) */
|
|
194
291
|
.hrl-table-wrap { overflow-x: auto; }
|
|
195
|
-
.hrl-table { width: 100%; border-collapse: collapse; font-size:
|
|
292
|
+
.hrl-table { width: 100%; border-collapse: collapse; font-size: var(--hrl-t-small); background: var(--hrl-card); border-radius: var(--hrl-radius); }
|
|
196
293
|
.hrl-table th, .hrl-table td { text-align: left; padding: .6rem .7rem; border-bottom: 1px solid var(--hrl-line); vertical-align: top; }
|
|
197
|
-
.hrl-table th { color: var(--hrl-muted); font-size:
|
|
294
|
+
.hrl-table th { color: var(--hrl-muted); font-size: var(--hrl-t-micro); text-transform: uppercase; letter-spacing: .03em; }
|
|
198
295
|
.hrl-table td.hrl-num, .hrl-table th.hrl-num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
199
296
|
.hrl-table--tight th, .hrl-table--tight td { padding: .5rem .35rem; }
|
|
200
|
-
|
|
297
|
+
.hrl-table td[data-label=""] > :is(a, button) + :is(a, button, form) { margin-left: var(--hrl-s-2); }
|
|
298
|
+
.hrl-table td[data-label=""] > :is(a, button) + form { display: inline-block; vertical-align: top; } /* a button_to beside a link, not under it */
|
|
299
|
+
.hrl-table td[data-label=""] > form + form { margin-top: var(--hrl-s-2); }
|
|
300
|
+
.hrl-table tr:has(.hrl-btn) > td, .hrl-table tr:has(.hrl-btn) td > .hrl-badge { vertical-align: middle; } /* text lines up with a 44px button */
|
|
301
|
+
/* Stack on phones and on the narrow content column beside the 768-1023px rail. */
|
|
302
|
+
@media (max-width: 640px), (min-width: 768px) and (max-width: 1023px) {
|
|
201
303
|
.hrl-table--stack thead { display: none; }
|
|
202
304
|
.hrl-table--stack tr { display: block; border-bottom: 6px solid var(--hrl-bg); }
|
|
203
305
|
.hrl-table--stack td { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .8rem; border-bottom: 1px dashed var(--hrl-line); overflow-wrap: anywhere; }
|
|
204
|
-
.hrl-table--stack td::before { content: attr(data-label); color: var(--hrl-muted); font-size:
|
|
306
|
+
.hrl-table--stack td::before { content: attr(data-label); color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
205
307
|
.hrl-table--stack td.hrl-num { text-align: left; }
|
|
206
308
|
.hrl-table--stack td.hrl-cell--void { display: none; }
|
|
309
|
+
.hrl-table--stack tr:last-child { border-bottom: 0; }
|
|
310
|
+
.hrl-table--stack td[data-label=""] { justify-content: flex-end; }
|
|
311
|
+
.hrl-table--stack td[data-label=""]::before { display: none; }
|
|
312
|
+
.hrl-table--stack td > .hrl-badge { align-self: center; } /* do not stretch beside a 44px button */
|
|
313
|
+
.hrl-table--stack td[data-label=""] > :is(a, button) + :is(a, button, form) { margin-left: 0; }
|
|
314
|
+
.hrl-table--stack td[data-label=""] > form + form { margin-top: 0; }
|
|
207
315
|
}
|
|
208
316
|
|
|
209
317
|
/* Forms */
|
|
210
318
|
.hrl-field { margin-bottom: .8rem; }
|
|
211
|
-
.hrl-field label { display: block; font-size:
|
|
212
|
-
.hrl-
|
|
319
|
+
.hrl-field label { display: block; font-size: var(--hrl-t-small); font-weight: 600; margin-bottom: .3rem; }
|
|
320
|
+
.hrl-hint { color: var(--hrl-muted); font-size: var(--hrl-t-small); margin-top: .25rem; }
|
|
213
321
|
/* Full-width text-ish controls only — checkboxes/radios/hidden must keep
|
|
214
322
|
their natural size (a bare [type] selector out-ranked .hrl-field--check
|
|
215
323
|
and blew checkboxes up to 100% width). */
|
|
216
324
|
.hrl-input,
|
|
217
325
|
.hrl-field input[type]:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
|
|
218
326
|
.hrl-field select, .hrl-field textarea {
|
|
219
|
-
width: 100%; min-height:
|
|
220
|
-
border: 1px solid var(--hrl-
|
|
327
|
+
width: 100%; min-height: var(--hrl-target); padding: .55rem .7rem; font: inherit; font-size: var(--hrl-t-body);
|
|
328
|
+
border: 1px solid var(--hrl-field); border-radius: var(--hrl-radius-sm);
|
|
221
329
|
background: var(--hrl-card); color: var(--hrl-ink);
|
|
222
330
|
}
|
|
223
331
|
.hrl-field textarea { min-height: 90px; resize: vertical; }
|
|
224
|
-
.hrl-
|
|
332
|
+
.hrl-filter { appearance: none; margin-bottom: var(--hrl-gap-target); } /* select_filter.js search box above its select */
|
|
333
|
+
.hrl-field--check { display: flex; align-items: center; gap: .55rem; min-height: var(--hrl-target); }
|
|
225
334
|
.hrl-field--check input {
|
|
226
335
|
width: 1.15rem; height: 1.15rem; min-height: 0; flex: 0 0 auto;
|
|
227
336
|
accent-color: var(--hrl-accent);
|
|
228
337
|
}
|
|
229
|
-
|
|
230
|
-
.hrl-
|
|
338
|
+
/* The 44px label is the tap target for the small box. */
|
|
339
|
+
.hrl-field--check label { margin-bottom: 0; font-weight: 600; flex: 1; min-height: var(--hrl-target); display: flex; align-items: center; }
|
|
340
|
+
.hrl-field--check label .hrl-hint { display: block; margin-top: 0; font-weight: 400; }
|
|
341
|
+
/* Rails wraps an invalid label/field in this div; contents keeps the stack, the re-pointed token reddens the border. */
|
|
342
|
+
.field_with_errors { display: contents; --hrl-field: var(--hrl-bad); }
|
|
343
|
+
.field_with_errors label { color: var(--hrl-bad); }
|
|
344
|
+
.hrl-errors { background: var(--hrl-bad-bg); color: var(--hrl-bad); border-radius: var(--hrl-radius-sm); padding: .7rem .9rem; margin-bottom: .85rem; font-size: var(--hrl-t-body); }
|
|
231
345
|
.hrl-errors ul { margin: .3rem 0 0; padding-left: 1.1rem; }
|
|
232
346
|
.hrl-form-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
|
|
233
347
|
.hrl-form-actions .hrl-btn { flex: 1; }
|
|
234
348
|
@media (min-width: 700px) { .hrl-form-actions .hrl-btn { flex: 0 0 auto; min-width: 9rem; } }
|
|
235
349
|
|
|
236
350
|
/* Radio chip row (kudos badges) */
|
|
237
|
-
.hrl-chiprow { display: flex; flex-wrap: wrap; gap:
|
|
351
|
+
.hrl-chiprow { display: flex; flex-wrap: wrap; gap: var(--hrl-gap-target); }
|
|
238
352
|
.hrl-chiprow label {
|
|
239
|
-
border: 1px solid var(--hrl-line); border-radius:
|
|
240
|
-
font-size:
|
|
353
|
+
border: 1px solid var(--hrl-line); border-radius: var(--hrl-radius-pill); padding: .4rem .8rem;
|
|
354
|
+
font-size: var(--hrl-t-small); font-weight: 600; cursor: pointer; min-height: var(--hrl-target); display: inline-flex; align-items: center;
|
|
355
|
+
min-width: var(--hrl-target); justify-content: center;
|
|
241
356
|
}
|
|
242
357
|
.hrl-chiprow input { position: absolute; opacity: 0; pointer-events: none; }
|
|
243
358
|
.hrl-chiprow input:checked + span, .hrl-chiprow label:has(input:checked) { background: var(--hrl-info-bg); border-color: var(--hrl-accent); color: var(--hrl-accent); }
|
|
359
|
+
/* The radio is invisible, so its focus ring goes on the chip. */
|
|
360
|
+
.hrl-chiprow label:has(input:focus-visible) { outline: var(--hrl-focus-w) solid var(--hrl-focus); outline-offset: var(--hrl-focus-offset); }
|
|
244
361
|
|
|
245
362
|
/* Feed items */
|
|
363
|
+
.hrl-feed { list-style: none; margin: 0; padding: 0; }
|
|
246
364
|
.hrl-feed__item { display: flex; gap: .7rem; padding: .8rem 0; border-bottom: 1px solid var(--hrl-line); }
|
|
247
365
|
.hrl-feed__item:last-child { border-bottom: 0; }
|
|
248
366
|
.hrl-avatar {
|
|
249
|
-
flex: 0 0 auto; width: 38px; height: 38px; border-radius:
|
|
367
|
+
flex: 0 0 auto; width: 38px; height: 38px; border-radius: var(--hrl-radius-pill);
|
|
250
368
|
background: var(--hrl-info-bg); color: var(--hrl-accent);
|
|
251
369
|
display: inline-flex; align-items: center; justify-content: center; font-weight: 700;
|
|
252
370
|
}
|
|
253
|
-
.hrl-feed__meta { color: var(--hrl-muted); font-size:
|
|
371
|
+
.hrl-feed__meta { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
254
372
|
.hrl-feed__body { min-width: 0; flex: 1; overflow-wrap: anywhere; }
|
|
255
373
|
|
|
256
374
|
/* Mention autocomplete menu */
|
|
257
375
|
.hrl-mention-wrap { position: relative; }
|
|
258
376
|
.hrl-mention-menu {
|
|
259
|
-
position: absolute; left: 0; right: 0; z-index:
|
|
377
|
+
position: absolute; left: 0; right: 0; z-index: var(--hrl-z-menu);
|
|
260
378
|
background: var(--hrl-card); border: 1px solid var(--hrl-line);
|
|
261
379
|
border-radius: var(--hrl-radius-sm); box-shadow: var(--hrl-shadow);
|
|
262
380
|
max-height: 14rem; overflow-y: auto;
|
|
263
381
|
}
|
|
264
382
|
.hrl-mention-menu button {
|
|
265
383
|
display: block; width: 100%; text-align: left; padding: .6rem .8rem;
|
|
266
|
-
background: none; border: 0; font: inherit; cursor: pointer; min-height:
|
|
384
|
+
background: none; border: 0; font: inherit; cursor: pointer; min-height: var(--hrl-target);
|
|
267
385
|
}
|
|
268
386
|
.hrl-mention-menu button:hover, .hrl-mention-menu button.hrl-active { background: var(--hrl-bg); }
|
|
269
387
|
|
|
270
388
|
/* Pagination */
|
|
271
|
-
.hrl-pager { display: flex; align-items: center; justify-content: center; gap: .8rem; margin: 1rem 0; font-size:
|
|
389
|
+
.hrl-pager { display: flex; align-items: center; justify-content: center; gap: .8rem; margin: 1rem 0; font-size: var(--hrl-t-small); }
|
|
272
390
|
.hrl-pager__info { color: var(--hrl-muted); }
|
|
273
391
|
|
|
274
392
|
/* Org chart tree + reporting-line chain */
|
|
@@ -282,45 +400,65 @@
|
|
|
282
400
|
}
|
|
283
401
|
.hrl-tree__card--me { border-color: var(--hrl-accent); background: var(--hrl-info-bg); }
|
|
284
402
|
.hrl-tree__name, .hrl-tree__role { min-width: 0; overflow-wrap: anywhere; }
|
|
285
|
-
.hrl-tree__name { font-weight:
|
|
286
|
-
.hrl-tree__role { color: var(--hrl-muted); font-size:
|
|
403
|
+
.hrl-tree__name { font-weight: 700; font-size: var(--hrl-t-small); }
|
|
404
|
+
.hrl-tree__role { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
287
405
|
.hrl-chainrow { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
|
|
288
406
|
.hrl-chainrow__step { display: inline-flex; align-items: center; gap: .4rem; min-width: 0; }
|
|
289
407
|
.hrl-chainrow .hrl-badge { white-space: normal; overflow-wrap: anywhere; }
|
|
290
408
|
.hrl-chainrow__arrow { color: var(--hrl-muted); }
|
|
291
409
|
|
|
292
410
|
/* Definition list */
|
|
293
|
-
.hrl-deflist { display: grid; grid-template-columns: minmax(7rem, auto) 1fr; gap: .35rem .9rem; font-size:
|
|
294
|
-
.hrl-deflist dt { color: var(--hrl-muted); }
|
|
411
|
+
.hrl-deflist { display: grid; grid-template-columns: minmax(7rem, auto) minmax(0, 1fr); gap: .35rem .9rem; font-size: var(--hrl-t-body); }
|
|
412
|
+
.hrl-deflist dt { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
295
413
|
.hrl-deflist dd { margin: 0; overflow-wrap: anywhere; }
|
|
296
414
|
|
|
297
415
|
/* Month grid (attendance) */
|
|
298
416
|
/* minmax(0, …): a bare 1fr never shrinks below its nowrap tags and pushes Sa/Su off a phone */
|
|
299
|
-
|
|
300
|
-
.hrl-
|
|
417
|
+
/* Phone: the grid alone reaches 4px from the card edge, with 2px gaps, so seven cells are 44px wide at 360px. */
|
|
418
|
+
.hrl-mgrid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
|
|
419
|
+
.hrl-card > .hrl-mgrid { margin-inline: calc(var(--hrl-s-1) - .95rem); }
|
|
420
|
+
@media (min-width: 481px) {
|
|
421
|
+
.hrl-mgrid { gap: 3px; }
|
|
422
|
+
.hrl-card > .hrl-mgrid { margin-inline: 0; }
|
|
423
|
+
}
|
|
424
|
+
.hrl-mgrid__head { text-align: center; color: var(--hrl-muted); font-size: var(--hrl-t-micro); padding: .2rem 0; }
|
|
301
425
|
.hrl-mgrid__day {
|
|
302
|
-
min-height:
|
|
303
|
-
border: 1px solid var(--hrl-line); padding: .2rem; font-size:
|
|
426
|
+
min-height: var(--hrl-target); border-radius: var(--hrl-radius-sm); background: var(--hrl-card);
|
|
427
|
+
border: 1px solid var(--hrl-line); padding: .2rem calc(var(--hrl-s-1) / 2); font-size: var(--hrl-t-small);
|
|
304
428
|
display: flex; flex-direction: column; gap: .1rem; position: relative;
|
|
305
429
|
}
|
|
306
|
-
|
|
307
|
-
.hrl-
|
|
308
|
-
.hrl-
|
|
430
|
+
/* 2px side padding on phones leaves "Today" its 32px in a 39px cell at 320px. */
|
|
431
|
+
@media (min-width: 481px) { .hrl-mgrid__day { padding-inline: .2rem; } }
|
|
432
|
+
.hrl-mgrid__day--muted { color: var(--hrl-muted); }
|
|
433
|
+
.hrl-mgrid__num { font-weight: 600; }
|
|
434
|
+
.hrl-mgrid__tag { font-size: var(--hrl-t-micro); color: var(--hrl-ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
435
|
+
/* The day number's link covers its whole cell and keeps the cell's ink, which holds 4.5:1 on every tint. */
|
|
436
|
+
.hrl-mgrid__day a { color: inherit; }
|
|
437
|
+
.hrl-mgrid__day a::after { content: ""; position: absolute; inset: -1px; border-radius: var(--hrl-radius-sm); }
|
|
438
|
+
/* Legend swatch: a cell in badge form; the state modifiers below tint it. */
|
|
439
|
+
.hrl-mgrid__key { background: var(--hrl-card); border: 1px solid var(--hrl-line); color: var(--hrl-ink-2); }
|
|
309
440
|
.hrl-mgrid__day--present { background: var(--hrl-ok-bg); border-color: transparent; }
|
|
310
441
|
.hrl-mgrid__day--half { background: linear-gradient(135deg, var(--hrl-ok-bg) 50%, var(--hrl-card) 50%); }
|
|
311
442
|
.hrl-mgrid__day--leave { background: var(--hrl-info-bg); border-color: transparent; }
|
|
443
|
+
.hrl-mgrid__day--half-leave { background: linear-gradient(135deg, var(--hrl-info-bg) 50%, var(--hrl-card) 50%); }
|
|
312
444
|
.hrl-mgrid__day--holiday { background: var(--hrl-warn-bg); border-color: transparent; }
|
|
313
445
|
.hrl-mgrid__day--weekend { background: var(--hrl-bg); }
|
|
314
446
|
.hrl-mgrid__day--absent { background: var(--hrl-bad-bg); border-color: transparent; }
|
|
315
|
-
.hrl-
|
|
447
|
+
.hrl-mgrid__day--today { box-shadow: inset 0 0 0 2px var(--hrl-accent); }
|
|
448
|
+
.hrl-mgrid__day--pending { border: 1px dashed var(--hrl-ink-2); }
|
|
449
|
+
.hrl-mgrid__flag { position: absolute; top: 3px; right: 4px; width: 7px; height: 7px; border-radius: var(--hrl-radius-pill); background: var(--hrl-bad); }
|
|
316
450
|
.hrl-legend { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
|
|
451
|
+
.hrl-legend .hrl-mgrid__flag { position: static; display: inline-block; }
|
|
452
|
+
@media (min-width: 768px) { .hrl-mgrid--cal .hrl-mgrid__day { min-height: 88px; } }
|
|
317
453
|
|
|
318
454
|
/* Utility */
|
|
319
455
|
.hrl-muted { color: var(--hrl-muted); }
|
|
320
|
-
.hrl-small { font-size:
|
|
456
|
+
.hrl-small { font-size: var(--hrl-t-small); }
|
|
321
457
|
.hrl-mt { margin-top: .85rem; }
|
|
322
458
|
.hrl-right { margin-left: auto; }
|
|
323
459
|
.hrl-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
|
|
460
|
+
.hrl-row--mb { margin-bottom: var(--hrl-s-3); }
|
|
461
|
+
.hrl-row--tabs > .hrl-btn { padding-inline: var(--hrl-s-2); } /* four approvals tabs fit one row beside the rail at 768px */
|
|
324
462
|
|
|
325
463
|
/* Long-form policy text. Constrained measure — a policy read across the
|
|
326
464
|
full width of a laptop is a policy nobody finishes. */
|
|
@@ -397,5 +535,5 @@
|
|
|
397
535
|
}
|
|
398
536
|
|
|
399
537
|
/* Notifications link, shown only when the host configures one. */
|
|
400
|
-
.hrl-bell { display: inline-flex; align-items: center; gap: .4rem; min-height:
|
|
401
|
-
.hrl-bell__count { min-width: 1.4rem; padding: 0 .4rem; border-radius:
|
|
538
|
+
.hrl-bell { display: inline-flex; align-items: center; gap: .4rem; min-height: var(--hrl-target); font-size: var(--hrl-t-small); font-weight: 600; }
|
|
539
|
+
.hrl-bell__count { min-width: 1.4rem; padding: 0 .4rem; border-radius: var(--hrl-radius-pill); background: var(--hrl-accent); color: var(--hrl-accent-ink); font-size: var(--hrl-t-micro); text-align: center; }
|
|
@@ -11,6 +11,36 @@ module HrLite
|
|
|
11
11
|
@employees = @employees.select { |user| visible.include?(user.id) } if visible
|
|
12
12
|
@records = AttendanceRecord.for_date(@date).where(user_id: @employees.map(&:id)).index_by(&:user_id)
|
|
13
13
|
@flagged_count = @records.values.count(&:flagged?)
|
|
14
|
+
# "How many people punched where": off-site punches count as one place.
|
|
15
|
+
@offices = OfficeLocation.active.to_a
|
|
16
|
+
@places = %i[check_in check_out].index_with do |side|
|
|
17
|
+
@records.values.select { |r| r.public_send("#{side}_at") }.map do |r|
|
|
18
|
+
OfficeLocation.place(r.public_send("#{side}_lat"), r.public_send("#{side}_lng"), @offices)&.split(" · ")&.first || "No GPS"
|
|
19
|
+
end.tally
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Mon–Fri hours per person (Saturday never counts) against
|
|
24
|
+
# config.work_hours[:week], pro rata for holidays and full-day leave.
|
|
25
|
+
# The current week counts only the days already over.
|
|
26
|
+
def week
|
|
27
|
+
@monday = parse_date_param(params[:date]).beginning_of_week(:monday)
|
|
28
|
+
calendar = WorkingCalendar.new(@monday..(@monday + 4))
|
|
29
|
+
days = (@monday..[ @monday + 4, Date.current - 1 ].min).select { |d| calendar.working_day?(d) }
|
|
30
|
+
visible = hr_access.visible_user_ids("attendance.view")
|
|
31
|
+
employees = HrLite.active_employees(on: @monday)
|
|
32
|
+
employees = employees.select { |user| visible.include?(user.id) } if visible
|
|
33
|
+
records = AttendanceRecord.where(user_id: employees.map(&:id), date: days).group_by(&:user_id)
|
|
34
|
+
on_leave = days.index_with { |d| LeaveRequest.active_on(d).where(half_day: false).pluck(:user_id) }
|
|
35
|
+
week_hours = HrLite.config.work_hours&.dig(:week)
|
|
36
|
+
|
|
37
|
+
@rows = employees.map do |user|
|
|
38
|
+
worked = records.fetch(user.id, []).filter_map(&:worked_duration)
|
|
39
|
+
hours = worked.sum / 3600.0
|
|
40
|
+
target = week_hours && week_hours * days.count { |d| on_leave[d].exclude?(user.id) } / 5.0
|
|
41
|
+
{ user: user, hours: hours, avg: worked.any? ? hours / worked.size : nil, target: target,
|
|
42
|
+
below: target.present? && target.positive? && hours < target }
|
|
43
|
+
end
|
|
14
44
|
end
|
|
15
45
|
|
|
16
46
|
# One employee's month + the regularization form for ?date=.
|
|
@@ -19,6 +49,7 @@ module HrLite
|
|
|
19
49
|
@month = parse_month_param(params[:month])
|
|
20
50
|
@day_status = DayStatus.new(user: @employee, range: @month.beginning_of_month..@month.end_of_month)
|
|
21
51
|
@counts = @day_status.counts
|
|
52
|
+
@counts[:absent] -= 1 if @month.all_month.cover?(Date.current) && @day_status.for(Date.current).kind == :absent
|
|
22
53
|
@edit_date = params[:date].present? ? parse_date_param(params[:date]) : nil
|
|
23
54
|
@edit_record = @edit_date && AttendanceRecord.find_or_initialize_by(user_id: @employee.id, date: @edit_date)
|
|
24
55
|
end
|
|
@@ -8,7 +8,7 @@ module HrLite
|
|
|
8
8
|
# screen and each sees only their own rows.
|
|
9
9
|
class ApprovalsController < ApplicationController
|
|
10
10
|
def index
|
|
11
|
-
@approvals = paginate(
|
|
11
|
+
@approvals = paginate(Approval.pending_for(hr_current_user).includes(:step, :subject).order(:created_at))
|
|
12
12
|
@delegations = ApprovalDelegation.live_on(Date.current)
|
|
13
13
|
.where(from_user_id: hr_current_user.id)
|
|
14
14
|
.includes(:to_user)
|
|
@@ -16,16 +16,5 @@ module HrLite
|
|
|
16
16
|
.where(to_user_id: hr_current_user.id)
|
|
17
17
|
.includes(:from_user)
|
|
18
18
|
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
# Rows addressed to this person, plus rows belonging to anybody who has
|
|
23
|
-
# delegated to them while they are away.
|
|
24
|
-
def mine
|
|
25
|
-
standing_in_for = ApprovalDelegation.live_on(Date.current)
|
|
26
|
-
.where(to_user_id: hr_current_user.id)
|
|
27
|
-
.pluck(:from_user_id)
|
|
28
|
-
Approval.pending.where(approver_id: [ hr_current_user.id, *standing_in_for ])
|
|
29
|
-
end
|
|
30
19
|
end
|
|
31
20
|
end
|
|
@@ -8,6 +8,13 @@ module HrLite
|
|
|
8
8
|
@record = AttendanceRecord.find_by(user_id: hr_current_user.id, date: Date.current)
|
|
9
9
|
@day_status = DayStatus.new(user: hr_current_user, range: @month.beginning_of_month..@month.end_of_month)
|
|
10
10
|
@counts = @day_status.counts
|
|
11
|
+
@counts[:absent] -= 1 if @month.all_month.cover?(Date.current) && @day_status.for(Date.current).kind == :absent
|
|
12
|
+
@pending_dates = RegularizationRequest.pending.where(user_id: hr_current_user.id, date: @month.all_month).pluck(:date).to_set
|
|
13
|
+
# ?day= opens the day detail; garbage (parsed as today), other months and the future open nothing.
|
|
14
|
+
day = parse_date_param(params[:day])
|
|
15
|
+
@day = day if params[:day] == day.iso8601 && @month.all_month.cover?(day) && day <= Date.current
|
|
16
|
+
@day_info = @day_status.for(@day) if @day
|
|
17
|
+
@day_ticket = RegularizationRequest.where(user_id: hr_current_user.id, date: @day).recent_first.first if @day
|
|
11
18
|
end
|
|
12
19
|
|
|
13
20
|
def check_in
|
|
@@ -4,6 +4,7 @@ module HrLite
|
|
|
4
4
|
year = params[:year].to_i
|
|
5
5
|
@year = year.between?(2000, 2100) ? year : Date.current.year
|
|
6
6
|
@holidays = Holiday.where(date: Date.new(@year, 1, 1)..Date.new(@year, 12, 31)).order(:date)
|
|
7
|
+
@next_holiday_id = Holiday.company_wide.upcoming.pick(:id)
|
|
7
8
|
end
|
|
8
9
|
end
|
|
9
10
|
end
|