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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +75 -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 +65 -10
- 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 +219 -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 +32 -10
- 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,165 @@
|
|
|
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-geo-help { margin: var(--hrl-s-2) 0 0; padding-left: var(--hrl-s-4); color: var(--hrl-ink); }
|
|
244
|
+
.hrl-geo-help li + li { margin-top: var(--hrl-s-1); }
|
|
245
|
+
.hrl-grid { display: grid; gap: .85rem; grid-template-columns: 1fr; margin-bottom: .85rem; }
|
|
157
246
|
.hrl-grid > * { min-width: 0; }
|
|
158
|
-
|
|
247
|
+
.hrl-grid > .hrl-card { margin-bottom: 0; }
|
|
248
|
+
@media (min-width: 1024px) { .hrl-grid--2 { grid-template-columns: 1fr 1fr; } }
|
|
159
249
|
|
|
160
250
|
/* KPI tiles */
|
|
161
251
|
.hrl-kpis { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; margin-bottom: .85rem; }
|
|
162
252
|
@media (min-width: 1100px) { .hrl-kpis { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
|
163
253
|
.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:
|
|
254
|
+
.hrl-kpi__value { font-size: var(--hrl-t-display); font-weight: 700; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
255
|
+
.hrl-kpi__label { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
256
|
+
/* Rupee tiles: a lakh amount fits a half-width 320px tile without splitting its digits. */
|
|
257
|
+
.hrl-kpis--money .hrl-kpi__value { font-size: var(--hrl-t-lead); }
|
|
258
|
+
@media (min-width: 481px) { .hrl-kpis--money .hrl-kpi__value { font-size: var(--hrl-t-display); } }
|
|
166
259
|
|
|
167
260
|
/* Buttons */
|
|
168
261
|
.hrl-btn {
|
|
169
262
|
display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
|
|
170
|
-
min-height:
|
|
263
|
+
min-height: var(--hrl-target); padding: .55rem 1rem; border-radius: var(--hrl-radius-sm);
|
|
171
264
|
border: 1px solid var(--hrl-line); background: var(--hrl-card); color: var(--hrl-ink);
|
|
172
|
-
font-size:
|
|
265
|
+
font-size: var(--hrl-t-body); font-weight: 600; cursor: pointer; text-decoration: none;
|
|
266
|
+
max-width: 100%; overflow-wrap: anywhere; text-align: center;
|
|
173
267
|
}
|
|
174
268
|
.hrl-btn--primary { background: var(--hrl-accent); border-color: var(--hrl-accent); color: var(--hrl-accent-ink); }
|
|
175
269
|
.hrl-btn--danger { color: var(--hrl-bad); border-color: var(--hrl-bad-bg); }
|
|
176
270
|
.hrl-btn--block { width: 100%; }
|
|
177
|
-
.hrl-btn--big { min-height: 58px; font-size:
|
|
271
|
+
.hrl-btn--big { min-height: 58px; font-size: var(--hrl-t-lead); }
|
|
178
272
|
.hrl-btn[disabled] { opacity: .55; cursor: default; }
|
|
273
|
+
summary.hrl-btn { list-style: none; }
|
|
274
|
+
summary.hrl-btn::-webkit-details-marker { display: none; } /* Safari before 18.4 draws it whatever the display */
|
|
179
275
|
|
|
180
276
|
/* Badges & chips */
|
|
181
277
|
.hrl-badge {
|
|
182
|
-
display: inline-
|
|
183
|
-
font-size:
|
|
184
|
-
border-radius:
|
|
185
|
-
white-space: nowrap;
|
|
278
|
+
display: inline-block;
|
|
279
|
+
font-size: var(--hrl-t-micro); font-weight: 600; line-height: var(--hrl-lh-tight); padding: .18rem .55rem;
|
|
280
|
+
border-radius: var(--hrl-radius-pill); background: var(--hrl-info-bg); color: var(--hrl-accent);
|
|
281
|
+
white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
|
|
186
282
|
}
|
|
187
283
|
.hrl-badge--ok { background: var(--hrl-ok-bg); color: var(--hrl-ok); }
|
|
188
284
|
.hrl-badge--warn { background: var(--hrl-warn-bg); color: var(--hrl-warn); }
|
|
189
285
|
.hrl-badge--bad { background: var(--hrl-bad-bg); color: var(--hrl-bad); }
|
|
190
|
-
.hrl-badge--muted { background: var(--hrl-bg); color: var(--hrl-
|
|
191
|
-
|
|
286
|
+
.hrl-badge--muted { background: var(--hrl-bg); color: var(--hrl-ink-2); }
|
|
287
|
+
/* Leave-type badge: ink text on grey; the HR-chosen colour (inline --hrl-type) is only the dot. */
|
|
288
|
+
.hrl-badge--type { background: var(--hrl-bg); color: var(--hrl-ink); }
|
|
289
|
+
.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); }
|
|
290
|
+
.hrl-mention { color: var(--hrl-accent); font-weight: 700; }
|
|
192
291
|
|
|
193
292
|
/* Tables (stack to label/value rows on phones) */
|
|
194
293
|
.hrl-table-wrap { overflow-x: auto; }
|
|
195
|
-
.hrl-table { width: 100%; border-collapse: collapse; font-size:
|
|
294
|
+
.hrl-table { width: 100%; border-collapse: collapse; font-size: var(--hrl-t-small); background: var(--hrl-card); border-radius: var(--hrl-radius); }
|
|
196
295
|
.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:
|
|
296
|
+
.hrl-table th { color: var(--hrl-muted); font-size: var(--hrl-t-micro); text-transform: uppercase; letter-spacing: .03em; }
|
|
198
297
|
.hrl-table td.hrl-num, .hrl-table th.hrl-num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
199
298
|
.hrl-table--tight th, .hrl-table--tight td { padding: .5rem .35rem; }
|
|
200
|
-
|
|
299
|
+
.hrl-table td[data-label=""] > :is(a, button) + :is(a, button, form) { margin-left: var(--hrl-s-2); }
|
|
300
|
+
.hrl-table td[data-label=""] > :is(a, button) + form { display: inline-block; vertical-align: top; } /* a button_to beside a link, not under it */
|
|
301
|
+
.hrl-table td[data-label=""] > form + form { margin-top: var(--hrl-s-2); }
|
|
302
|
+
.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 */
|
|
303
|
+
/* Stack on phones and on the narrow content column beside the 768-1023px rail. */
|
|
304
|
+
@media (max-width: 640px), (min-width: 768px) and (max-width: 1023px) {
|
|
201
305
|
.hrl-table--stack thead { display: none; }
|
|
202
306
|
.hrl-table--stack tr { display: block; border-bottom: 6px solid var(--hrl-bg); }
|
|
203
307
|
.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:
|
|
308
|
+
.hrl-table--stack td::before { content: attr(data-label); color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
205
309
|
.hrl-table--stack td.hrl-num { text-align: left; }
|
|
206
310
|
.hrl-table--stack td.hrl-cell--void { display: none; }
|
|
311
|
+
.hrl-table--stack tr:last-child { border-bottom: 0; }
|
|
312
|
+
.hrl-table--stack td[data-label=""] { justify-content: flex-end; }
|
|
313
|
+
.hrl-table--stack td[data-label=""]::before { display: none; }
|
|
314
|
+
.hrl-table--stack td > .hrl-badge { align-self: center; } /* do not stretch beside a 44px button */
|
|
315
|
+
.hrl-table--stack td[data-label=""] > :is(a, button) + :is(a, button, form) { margin-left: 0; }
|
|
316
|
+
.hrl-table--stack td[data-label=""] > form + form { margin-top: 0; }
|
|
207
317
|
}
|
|
208
318
|
|
|
209
319
|
/* Forms */
|
|
210
320
|
.hrl-field { margin-bottom: .8rem; }
|
|
211
|
-
.hrl-field label { display: block; font-size:
|
|
212
|
-
.hrl-
|
|
321
|
+
.hrl-field label { display: block; font-size: var(--hrl-t-small); font-weight: 600; margin-bottom: .3rem; }
|
|
322
|
+
.hrl-hint { color: var(--hrl-muted); font-size: var(--hrl-t-small); margin-top: .25rem; }
|
|
213
323
|
/* Full-width text-ish controls only — checkboxes/radios/hidden must keep
|
|
214
324
|
their natural size (a bare [type] selector out-ranked .hrl-field--check
|
|
215
325
|
and blew checkboxes up to 100% width). */
|
|
216
326
|
.hrl-input,
|
|
217
327
|
.hrl-field input[type]:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
|
|
218
328
|
.hrl-field select, .hrl-field textarea {
|
|
219
|
-
width: 100%; min-height:
|
|
220
|
-
border: 1px solid var(--hrl-
|
|
329
|
+
width: 100%; min-height: var(--hrl-target); padding: .55rem .7rem; font: inherit; font-size: var(--hrl-t-body);
|
|
330
|
+
border: 1px solid var(--hrl-field); border-radius: var(--hrl-radius-sm);
|
|
221
331
|
background: var(--hrl-card); color: var(--hrl-ink);
|
|
222
332
|
}
|
|
223
333
|
.hrl-field textarea { min-height: 90px; resize: vertical; }
|
|
224
|
-
.hrl-
|
|
334
|
+
.hrl-filter { appearance: none; margin-bottom: var(--hrl-gap-target); } /* select_filter.js search box above its select */
|
|
335
|
+
.hrl-field--check { display: flex; align-items: center; gap: .55rem; min-height: var(--hrl-target); }
|
|
225
336
|
.hrl-field--check input {
|
|
226
337
|
width: 1.15rem; height: 1.15rem; min-height: 0; flex: 0 0 auto;
|
|
227
338
|
accent-color: var(--hrl-accent);
|
|
228
339
|
}
|
|
229
|
-
|
|
230
|
-
.hrl-
|
|
340
|
+
/* The 44px label is the tap target for the small box. */
|
|
341
|
+
.hrl-field--check label { margin-bottom: 0; font-weight: 600; flex: 1; min-height: var(--hrl-target); display: flex; align-items: center; }
|
|
342
|
+
.hrl-field--check label .hrl-hint { display: block; margin-top: 0; font-weight: 400; }
|
|
343
|
+
/* Rails wraps an invalid label/field in this div; contents keeps the stack, the re-pointed token reddens the border. */
|
|
344
|
+
.field_with_errors { display: contents; --hrl-field: var(--hrl-bad); }
|
|
345
|
+
.field_with_errors label { color: var(--hrl-bad); }
|
|
346
|
+
.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
347
|
.hrl-errors ul { margin: .3rem 0 0; padding-left: 1.1rem; }
|
|
232
348
|
.hrl-form-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
|
|
233
349
|
.hrl-form-actions .hrl-btn { flex: 1; }
|
|
234
350
|
@media (min-width: 700px) { .hrl-form-actions .hrl-btn { flex: 0 0 auto; min-width: 9rem; } }
|
|
235
351
|
|
|
236
352
|
/* Radio chip row (kudos badges) */
|
|
237
|
-
.hrl-chiprow { display: flex; flex-wrap: wrap; gap:
|
|
353
|
+
.hrl-chiprow { display: flex; flex-wrap: wrap; gap: var(--hrl-gap-target); }
|
|
238
354
|
.hrl-chiprow label {
|
|
239
|
-
border: 1px solid var(--hrl-line); border-radius:
|
|
240
|
-
font-size:
|
|
355
|
+
border: 1px solid var(--hrl-line); border-radius: var(--hrl-radius-pill); padding: .4rem .8rem;
|
|
356
|
+
font-size: var(--hrl-t-small); font-weight: 600; cursor: pointer; min-height: var(--hrl-target); display: inline-flex; align-items: center;
|
|
357
|
+
min-width: var(--hrl-target); justify-content: center;
|
|
241
358
|
}
|
|
242
359
|
.hrl-chiprow input { position: absolute; opacity: 0; pointer-events: none; }
|
|
243
360
|
.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); }
|
|
361
|
+
/* The radio is invisible, so its focus ring goes on the chip. */
|
|
362
|
+
.hrl-chiprow label:has(input:focus-visible) { outline: var(--hrl-focus-w) solid var(--hrl-focus); outline-offset: var(--hrl-focus-offset); }
|
|
244
363
|
|
|
245
364
|
/* Feed items */
|
|
365
|
+
.hrl-feed { list-style: none; margin: 0; padding: 0; }
|
|
246
366
|
.hrl-feed__item { display: flex; gap: .7rem; padding: .8rem 0; border-bottom: 1px solid var(--hrl-line); }
|
|
247
367
|
.hrl-feed__item:last-child { border-bottom: 0; }
|
|
248
368
|
.hrl-avatar {
|
|
249
|
-
flex: 0 0 auto; width: 38px; height: 38px; border-radius:
|
|
369
|
+
flex: 0 0 auto; width: 38px; height: 38px; border-radius: var(--hrl-radius-pill);
|
|
250
370
|
background: var(--hrl-info-bg); color: var(--hrl-accent);
|
|
251
371
|
display: inline-flex; align-items: center; justify-content: center; font-weight: 700;
|
|
252
372
|
}
|
|
253
|
-
.hrl-feed__meta { color: var(--hrl-muted); font-size:
|
|
373
|
+
.hrl-feed__meta { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
254
374
|
.hrl-feed__body { min-width: 0; flex: 1; overflow-wrap: anywhere; }
|
|
255
375
|
|
|
256
376
|
/* Mention autocomplete menu */
|
|
257
377
|
.hrl-mention-wrap { position: relative; }
|
|
258
378
|
.hrl-mention-menu {
|
|
259
|
-
position: absolute; left: 0; right: 0; z-index:
|
|
379
|
+
position: absolute; left: 0; right: 0; z-index: var(--hrl-z-menu);
|
|
260
380
|
background: var(--hrl-card); border: 1px solid var(--hrl-line);
|
|
261
381
|
border-radius: var(--hrl-radius-sm); box-shadow: var(--hrl-shadow);
|
|
262
382
|
max-height: 14rem; overflow-y: auto;
|
|
263
383
|
}
|
|
264
384
|
.hrl-mention-menu button {
|
|
265
385
|
display: block; width: 100%; text-align: left; padding: .6rem .8rem;
|
|
266
|
-
background: none; border: 0; font: inherit; cursor: pointer; min-height:
|
|
386
|
+
background: none; border: 0; font: inherit; cursor: pointer; min-height: var(--hrl-target);
|
|
267
387
|
}
|
|
268
388
|
.hrl-mention-menu button:hover, .hrl-mention-menu button.hrl-active { background: var(--hrl-bg); }
|
|
269
389
|
|
|
270
390
|
/* Pagination */
|
|
271
|
-
.hrl-pager { display: flex; align-items: center; justify-content: center; gap: .8rem; margin: 1rem 0; font-size:
|
|
391
|
+
.hrl-pager { display: flex; align-items: center; justify-content: center; gap: .8rem; margin: 1rem 0; font-size: var(--hrl-t-small); }
|
|
272
392
|
.hrl-pager__info { color: var(--hrl-muted); }
|
|
273
393
|
|
|
274
394
|
/* Org chart tree + reporting-line chain */
|
|
@@ -282,45 +402,65 @@
|
|
|
282
402
|
}
|
|
283
403
|
.hrl-tree__card--me { border-color: var(--hrl-accent); background: var(--hrl-info-bg); }
|
|
284
404
|
.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:
|
|
405
|
+
.hrl-tree__name { font-weight: 700; font-size: var(--hrl-t-small); }
|
|
406
|
+
.hrl-tree__role { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
287
407
|
.hrl-chainrow { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
|
|
288
408
|
.hrl-chainrow__step { display: inline-flex; align-items: center; gap: .4rem; min-width: 0; }
|
|
289
409
|
.hrl-chainrow .hrl-badge { white-space: normal; overflow-wrap: anywhere; }
|
|
290
410
|
.hrl-chainrow__arrow { color: var(--hrl-muted); }
|
|
291
411
|
|
|
292
412
|
/* 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); }
|
|
413
|
+
.hrl-deflist { display: grid; grid-template-columns: minmax(7rem, auto) minmax(0, 1fr); gap: .35rem .9rem; font-size: var(--hrl-t-body); }
|
|
414
|
+
.hrl-deflist dt { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
295
415
|
.hrl-deflist dd { margin: 0; overflow-wrap: anywhere; }
|
|
296
416
|
|
|
297
417
|
/* Month grid (attendance) */
|
|
298
418
|
/* minmax(0, …): a bare 1fr never shrinks below its nowrap tags and pushes Sa/Su off a phone */
|
|
299
|
-
|
|
300
|
-
.hrl-
|
|
419
|
+
/* Phone: the grid alone reaches 4px from the card edge, with 2px gaps, so seven cells are 44px wide at 360px. */
|
|
420
|
+
.hrl-mgrid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
|
|
421
|
+
.hrl-card > .hrl-mgrid { margin-inline: calc(var(--hrl-s-1) - .95rem); }
|
|
422
|
+
@media (min-width: 481px) {
|
|
423
|
+
.hrl-mgrid { gap: 3px; }
|
|
424
|
+
.hrl-card > .hrl-mgrid { margin-inline: 0; }
|
|
425
|
+
}
|
|
426
|
+
.hrl-mgrid__head { text-align: center; color: var(--hrl-muted); font-size: var(--hrl-t-micro); padding: .2rem 0; }
|
|
301
427
|
.hrl-mgrid__day {
|
|
302
|
-
min-height:
|
|
303
|
-
border: 1px solid var(--hrl-line); padding: .2rem; font-size:
|
|
428
|
+
min-height: var(--hrl-target); border-radius: var(--hrl-radius-sm); background: var(--hrl-card);
|
|
429
|
+
border: 1px solid var(--hrl-line); padding: .2rem calc(var(--hrl-s-1) / 2); font-size: var(--hrl-t-small);
|
|
304
430
|
display: flex; flex-direction: column; gap: .1rem; position: relative;
|
|
305
431
|
}
|
|
306
|
-
|
|
307
|
-
.hrl-
|
|
308
|
-
.hrl-
|
|
432
|
+
/* 2px side padding on phones leaves "Today" its 32px in a 39px cell at 320px. */
|
|
433
|
+
@media (min-width: 481px) { .hrl-mgrid__day { padding-inline: .2rem; } }
|
|
434
|
+
.hrl-mgrid__day--muted { color: var(--hrl-muted); }
|
|
435
|
+
.hrl-mgrid__num { font-weight: 600; }
|
|
436
|
+
.hrl-mgrid__tag { font-size: var(--hrl-t-micro); color: var(--hrl-ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
437
|
+
/* The day number's link covers its whole cell and keeps the cell's ink, which holds 4.5:1 on every tint. */
|
|
438
|
+
.hrl-mgrid__day a { color: inherit; }
|
|
439
|
+
.hrl-mgrid__day a::after { content: ""; position: absolute; inset: -1px; border-radius: var(--hrl-radius-sm); }
|
|
440
|
+
/* Legend swatch: a cell in badge form; the state modifiers below tint it. */
|
|
441
|
+
.hrl-mgrid__key { background: var(--hrl-card); border: 1px solid var(--hrl-line); color: var(--hrl-ink-2); }
|
|
309
442
|
.hrl-mgrid__day--present { background: var(--hrl-ok-bg); border-color: transparent; }
|
|
310
443
|
.hrl-mgrid__day--half { background: linear-gradient(135deg, var(--hrl-ok-bg) 50%, var(--hrl-card) 50%); }
|
|
311
444
|
.hrl-mgrid__day--leave { background: var(--hrl-info-bg); border-color: transparent; }
|
|
445
|
+
.hrl-mgrid__day--half-leave { background: linear-gradient(135deg, var(--hrl-info-bg) 50%, var(--hrl-card) 50%); }
|
|
312
446
|
.hrl-mgrid__day--holiday { background: var(--hrl-warn-bg); border-color: transparent; }
|
|
313
447
|
.hrl-mgrid__day--weekend { background: var(--hrl-bg); }
|
|
314
448
|
.hrl-mgrid__day--absent { background: var(--hrl-bad-bg); border-color: transparent; }
|
|
315
|
-
.hrl-
|
|
449
|
+
.hrl-mgrid__day--today { box-shadow: inset 0 0 0 2px var(--hrl-accent); }
|
|
450
|
+
.hrl-mgrid__day--pending { border: 1px dashed var(--hrl-ink-2); }
|
|
451
|
+
.hrl-mgrid__flag { position: absolute; top: 3px; right: 4px; width: 7px; height: 7px; border-radius: var(--hrl-radius-pill); background: var(--hrl-bad); }
|
|
316
452
|
.hrl-legend { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
|
|
453
|
+
.hrl-legend .hrl-mgrid__flag { position: static; display: inline-block; }
|
|
454
|
+
@media (min-width: 768px) { .hrl-mgrid--cal .hrl-mgrid__day { min-height: 88px; } }
|
|
317
455
|
|
|
318
456
|
/* Utility */
|
|
319
457
|
.hrl-muted { color: var(--hrl-muted); }
|
|
320
|
-
.hrl-small { font-size:
|
|
458
|
+
.hrl-small { font-size: var(--hrl-t-small); }
|
|
321
459
|
.hrl-mt { margin-top: .85rem; }
|
|
322
460
|
.hrl-right { margin-left: auto; }
|
|
323
461
|
.hrl-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
|
|
462
|
+
.hrl-row--mb { margin-bottom: var(--hrl-s-3); }
|
|
463
|
+
.hrl-row--tabs > .hrl-btn { padding-inline: var(--hrl-s-2); } /* four approvals tabs fit one row beside the rail at 768px */
|
|
324
464
|
|
|
325
465
|
/* Long-form policy text. Constrained measure — a policy read across the
|
|
326
466
|
full width of a laptop is a policy nobody finishes. */
|
|
@@ -397,5 +537,5 @@
|
|
|
397
537
|
}
|
|
398
538
|
|
|
399
539
|
/* 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:
|
|
540
|
+
.hrl-bell { display: inline-flex; align-items: center; gap: .4rem; min-height: var(--hrl-target); font-size: var(--hrl-t-small); font-weight: 600; }
|
|
541
|
+
.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
|