hr_lite 0.21.2 → 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 +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 +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 +223 -83
- 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 +3 -3
- 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,123 +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-
|
|
157
|
-
|
|
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; }
|
|
244
|
+
.hrl-grid > * { min-width: 0; }
|
|
245
|
+
.hrl-grid > .hrl-card { margin-bottom: 0; }
|
|
246
|
+
@media (min-width: 1024px) { .hrl-grid--2 { grid-template-columns: 1fr 1fr; } }
|
|
158
247
|
|
|
159
248
|
/* KPI tiles */
|
|
160
|
-
.hrl-kpis { display: grid; grid-template-columns: repeat(2, 1fr); gap: .6rem; margin-bottom: .85rem; }
|
|
161
|
-
@media (min-width:
|
|
249
|
+
.hrl-kpis { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; margin-bottom: .85rem; }
|
|
250
|
+
@media (min-width: 1100px) { .hrl-kpis { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
|
162
251
|
.hrl-kpi { background: var(--hrl-card); border: 1px solid var(--hrl-line); border-radius: var(--hrl-radius); padding: .7rem .8rem; }
|
|
163
|
-
.hrl-kpi__value { font-size:
|
|
164
|
-
.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); } }
|
|
165
257
|
|
|
166
258
|
/* Buttons */
|
|
167
259
|
.hrl-btn {
|
|
168
260
|
display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
|
|
169
|
-
min-height:
|
|
261
|
+
min-height: var(--hrl-target); padding: .55rem 1rem; border-radius: var(--hrl-radius-sm);
|
|
170
262
|
border: 1px solid var(--hrl-line); background: var(--hrl-card); color: var(--hrl-ink);
|
|
171
|
-
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;
|
|
172
265
|
}
|
|
173
266
|
.hrl-btn--primary { background: var(--hrl-accent); border-color: var(--hrl-accent); color: var(--hrl-accent-ink); }
|
|
174
267
|
.hrl-btn--danger { color: var(--hrl-bad); border-color: var(--hrl-bad-bg); }
|
|
175
268
|
.hrl-btn--block { width: 100%; }
|
|
176
|
-
.hrl-btn--big { min-height: 58px; font-size:
|
|
269
|
+
.hrl-btn--big { min-height: 58px; font-size: var(--hrl-t-lead); }
|
|
177
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 */
|
|
178
273
|
|
|
179
274
|
/* Badges & chips */
|
|
180
275
|
.hrl-badge {
|
|
181
|
-
display: inline-
|
|
182
|
-
font-size:
|
|
183
|
-
border-radius:
|
|
184
|
-
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;
|
|
185
280
|
}
|
|
186
281
|
.hrl-badge--ok { background: var(--hrl-ok-bg); color: var(--hrl-ok); }
|
|
187
282
|
.hrl-badge--warn { background: var(--hrl-warn-bg); color: var(--hrl-warn); }
|
|
188
283
|
.hrl-badge--bad { background: var(--hrl-bad-bg); color: var(--hrl-bad); }
|
|
189
|
-
.hrl-badge--muted { background: var(--hrl-bg); color: var(--hrl-
|
|
190
|
-
|
|
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; }
|
|
191
289
|
|
|
192
290
|
/* Tables (stack to label/value rows on phones) */
|
|
193
291
|
.hrl-table-wrap { overflow-x: auto; }
|
|
194
|
-
.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); }
|
|
195
293
|
.hrl-table th, .hrl-table td { text-align: left; padding: .6rem .7rem; border-bottom: 1px solid var(--hrl-line); vertical-align: top; }
|
|
196
|
-
.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; }
|
|
197
295
|
.hrl-table td.hrl-num, .hrl-table th.hrl-num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
198
296
|
.hrl-table--tight th, .hrl-table--tight td { padding: .5rem .35rem; }
|
|
199
|
-
|
|
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) {
|
|
200
303
|
.hrl-table--stack thead { display: none; }
|
|
201
304
|
.hrl-table--stack tr { display: block; border-bottom: 6px solid var(--hrl-bg); }
|
|
202
|
-
.hrl-table--stack td { display: flex; justify-content: space-between; gap: .8rem; border-bottom: 1px dashed var(--hrl-line); }
|
|
203
|
-
.hrl-table--stack td::before { content: attr(data-label); color: var(--hrl-muted); font-size:
|
|
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; }
|
|
306
|
+
.hrl-table--stack td::before { content: attr(data-label); color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
204
307
|
.hrl-table--stack td.hrl-num { text-align: left; }
|
|
205
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; }
|
|
206
315
|
}
|
|
207
316
|
|
|
208
317
|
/* Forms */
|
|
209
318
|
.hrl-field { margin-bottom: .8rem; }
|
|
210
|
-
.hrl-field label { display: block; font-size:
|
|
211
|
-
.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; }
|
|
212
321
|
/* Full-width text-ish controls only — checkboxes/radios/hidden must keep
|
|
213
322
|
their natural size (a bare [type] selector out-ranked .hrl-field--check
|
|
214
323
|
and blew checkboxes up to 100% width). */
|
|
215
324
|
.hrl-input,
|
|
216
325
|
.hrl-field input[type]:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
|
|
217
326
|
.hrl-field select, .hrl-field textarea {
|
|
218
|
-
width: 100%; min-height:
|
|
219
|
-
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);
|
|
220
329
|
background: var(--hrl-card); color: var(--hrl-ink);
|
|
221
330
|
}
|
|
222
331
|
.hrl-field textarea { min-height: 90px; resize: vertical; }
|
|
223
|
-
.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); }
|
|
224
334
|
.hrl-field--check input {
|
|
225
335
|
width: 1.15rem; height: 1.15rem; min-height: 0; flex: 0 0 auto;
|
|
226
336
|
accent-color: var(--hrl-accent);
|
|
227
337
|
}
|
|
228
|
-
|
|
229
|
-
.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); }
|
|
230
345
|
.hrl-errors ul { margin: .3rem 0 0; padding-left: 1.1rem; }
|
|
231
346
|
.hrl-form-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
|
|
232
347
|
.hrl-form-actions .hrl-btn { flex: 1; }
|
|
233
348
|
@media (min-width: 700px) { .hrl-form-actions .hrl-btn { flex: 0 0 auto; min-width: 9rem; } }
|
|
234
349
|
|
|
235
350
|
/* Radio chip row (kudos badges) */
|
|
236
|
-
.hrl-chiprow { display: flex; flex-wrap: wrap; gap:
|
|
351
|
+
.hrl-chiprow { display: flex; flex-wrap: wrap; gap: var(--hrl-gap-target); }
|
|
237
352
|
.hrl-chiprow label {
|
|
238
|
-
border: 1px solid var(--hrl-line); border-radius:
|
|
239
|
-
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;
|
|
240
356
|
}
|
|
241
357
|
.hrl-chiprow input { position: absolute; opacity: 0; pointer-events: none; }
|
|
242
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); }
|
|
243
361
|
|
|
244
362
|
/* Feed items */
|
|
363
|
+
.hrl-feed { list-style: none; margin: 0; padding: 0; }
|
|
245
364
|
.hrl-feed__item { display: flex; gap: .7rem; padding: .8rem 0; border-bottom: 1px solid var(--hrl-line); }
|
|
246
365
|
.hrl-feed__item:last-child { border-bottom: 0; }
|
|
247
366
|
.hrl-avatar {
|
|
248
|
-
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);
|
|
249
368
|
background: var(--hrl-info-bg); color: var(--hrl-accent);
|
|
250
369
|
display: inline-flex; align-items: center; justify-content: center; font-weight: 700;
|
|
251
370
|
}
|
|
252
|
-
.hrl-feed__meta { color: var(--hrl-muted); font-size:
|
|
371
|
+
.hrl-feed__meta { color: var(--hrl-muted); font-size: var(--hrl-t-small); }
|
|
253
372
|
.hrl-feed__body { min-width: 0; flex: 1; overflow-wrap: anywhere; }
|
|
254
373
|
|
|
255
374
|
/* Mention autocomplete menu */
|
|
256
375
|
.hrl-mention-wrap { position: relative; }
|
|
257
376
|
.hrl-mention-menu {
|
|
258
|
-
position: absolute; left: 0; right: 0; z-index:
|
|
377
|
+
position: absolute; left: 0; right: 0; z-index: var(--hrl-z-menu);
|
|
259
378
|
background: var(--hrl-card); border: 1px solid var(--hrl-line);
|
|
260
379
|
border-radius: var(--hrl-radius-sm); box-shadow: var(--hrl-shadow);
|
|
261
380
|
max-height: 14rem; overflow-y: auto;
|
|
262
381
|
}
|
|
263
382
|
.hrl-mention-menu button {
|
|
264
383
|
display: block; width: 100%; text-align: left; padding: .6rem .8rem;
|
|
265
|
-
background: none; border: 0; font: inherit; cursor: pointer; min-height:
|
|
384
|
+
background: none; border: 0; font: inherit; cursor: pointer; min-height: var(--hrl-target);
|
|
266
385
|
}
|
|
267
386
|
.hrl-mention-menu button:hover, .hrl-mention-menu button.hrl-active { background: var(--hrl-bg); }
|
|
268
387
|
|
|
269
388
|
/* Pagination */
|
|
270
|
-
.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); }
|
|
271
390
|
.hrl-pager__info { color: var(--hrl-muted); }
|
|
272
391
|
|
|
273
392
|
/* Org chart tree + reporting-line chain */
|
|
@@ -281,48 +400,69 @@
|
|
|
281
400
|
}
|
|
282
401
|
.hrl-tree__card--me { border-color: var(--hrl-accent); background: var(--hrl-info-bg); }
|
|
283
402
|
.hrl-tree__name, .hrl-tree__role { min-width: 0; overflow-wrap: anywhere; }
|
|
284
|
-
.hrl-tree__name { font-weight:
|
|
285
|
-
.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); }
|
|
286
405
|
.hrl-chainrow { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
|
|
287
406
|
.hrl-chainrow__step { display: inline-flex; align-items: center; gap: .4rem; min-width: 0; }
|
|
288
407
|
.hrl-chainrow .hrl-badge { white-space: normal; overflow-wrap: anywhere; }
|
|
289
408
|
.hrl-chainrow__arrow { color: var(--hrl-muted); }
|
|
290
409
|
|
|
291
410
|
/* Definition list */
|
|
292
|
-
.hrl-deflist { display: grid; grid-template-columns: minmax(7rem, auto) 1fr; gap: .35rem .9rem; font-size:
|
|
293
|
-
.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); }
|
|
294
413
|
.hrl-deflist dd { margin: 0; overflow-wrap: anywhere; }
|
|
295
414
|
|
|
296
415
|
/* Month grid (attendance) */
|
|
297
|
-
|
|
298
|
-
|
|
416
|
+
/* minmax(0, …): a bare 1fr never shrinks below its nowrap tags and pushes Sa/Su off a phone */
|
|
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; }
|
|
299
425
|
.hrl-mgrid__day {
|
|
300
|
-
min-height:
|
|
301
|
-
border: 1px solid var(--hrl-line); padding: .2rem
|
|
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);
|
|
302
428
|
display: flex; flex-direction: column; gap: .1rem; position: relative;
|
|
303
429
|
}
|
|
304
|
-
|
|
305
|
-
.hrl-
|
|
306
|
-
.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); }
|
|
307
440
|
.hrl-mgrid__day--present { background: var(--hrl-ok-bg); border-color: transparent; }
|
|
308
441
|
.hrl-mgrid__day--half { background: linear-gradient(135deg, var(--hrl-ok-bg) 50%, var(--hrl-card) 50%); }
|
|
309
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%); }
|
|
310
444
|
.hrl-mgrid__day--holiday { background: var(--hrl-warn-bg); border-color: transparent; }
|
|
311
445
|
.hrl-mgrid__day--weekend { background: var(--hrl-bg); }
|
|
312
446
|
.hrl-mgrid__day--absent { background: var(--hrl-bad-bg); border-color: transparent; }
|
|
313
|
-
.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); }
|
|
314
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; } }
|
|
315
453
|
|
|
316
454
|
/* Utility */
|
|
317
455
|
.hrl-muted { color: var(--hrl-muted); }
|
|
318
|
-
.hrl-small { font-size:
|
|
456
|
+
.hrl-small { font-size: var(--hrl-t-small); }
|
|
319
457
|
.hrl-mt { margin-top: .85rem; }
|
|
320
458
|
.hrl-right { margin-left: auto; }
|
|
321
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 */
|
|
322
462
|
|
|
323
463
|
/* Long-form policy text. Constrained measure — a policy read across the
|
|
324
464
|
full width of a laptop is a policy nobody finishes. */
|
|
325
|
-
.hrl-prose { max-width: 68ch; }
|
|
465
|
+
.hrl-prose { max-width: 68ch; overflow-wrap: anywhere; }
|
|
326
466
|
.hrl-prose p { margin: 0 0 .8rem; line-height: 1.6; }
|
|
327
467
|
.hrl-prose p:last-child { margin-bottom: 0; }
|
|
328
468
|
|
|
@@ -395,5 +535,5 @@
|
|
|
395
535
|
}
|
|
396
536
|
|
|
397
537
|
/* Notifications link, shown only when the host configures one. */
|
|
398
|
-
.hrl-bell { display: inline-flex; align-items: center; gap: .4rem; min-height:
|
|
399
|
-
.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
|