alpe-temp 1.0.3 → 1.0.4
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.
- package/.env.example +1 -1
- package/backend-project/server-err.txt +0 -0
- package/backend-project/server-out.txt +6 -0
- package/backend-project/src/modules/reports/reports.controller.js +147 -0
- package/backend-project/src/modules/reports/reports.routes.js +1 -0
- package/backend-project/test-all-routes.js +294 -0
- package/bin/epms.js +57 -92
- package/frontend-project/dist/assets/index-CRG9iE0k.css +1 -0
- package/frontend-project/dist/assets/{index-Bo0aORq7.js → index-LpBGz8lQ.js} +8 -8
- package/frontend-project/dist/index.html +2 -3
- package/frontend-project/index.html +0 -1
- package/frontend-project/src/Auth/Login.jsx +1 -1
- package/frontend-project/src/Auth/Register.jsx +0 -1
- package/frontend-project/src/Intro.jsx +1 -1
- package/frontend-project/src/api/ApiClient.js +2 -1
- package/frontend-project/src/config.js +4 -4
- package/frontend-project/src/layouts/BottomNav.jsx +1 -1
- package/frontend-project/src/layouts/TopNav.jsx +0 -1
- package/frontend-project/src/pages/Reports.jsx +38 -6
- package/package.json +2 -14
- package/frontend-project/dist/assets/index-BXwcQ8Za.css +0 -1
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
|
6
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
6
|
<title>HRMS - DAB Enterprise LTD</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-LpBGz8lQ.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CRG9iE0k.css">
|
|
10
9
|
</head>
|
|
11
10
|
<body>
|
|
12
11
|
<div id="root"></div>
|
|
@@ -36,7 +36,7 @@ export default function Login() {
|
|
|
36
36
|
<div className="hidden bg-[#008A75] sm:flex flex-col w-5/12 p-10 text-white">
|
|
37
37
|
<div className="mb-auto">
|
|
38
38
|
<div className="flex items-center gap-2 mb-1">
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
<h1 className="text-[22px] font-bold tracking-tight mt-3">HRMS</h1>
|
|
41
41
|
</div>
|
|
42
42
|
<p className="text-[10px] text-white/60 font-medium uppercase tracking-widest">Human Resource Management System</p>
|
|
@@ -49,7 +49,6 @@ export default function Register() {
|
|
|
49
49
|
<div className="hidden sm:flex bg-[#008A75] flex-col w-5/12 p-10 text-white">
|
|
50
50
|
<div className="mb-auto">
|
|
51
51
|
<div className="flex items-center gap-2 mb-1">
|
|
52
|
-
<img src="/logo.png" alt="HRMS" className="w-8 h-10" />
|
|
53
52
|
<h1 className="text-[22px] font-bold tracking-tight mt-3">HRMS</h1>
|
|
54
53
|
</div>
|
|
55
54
|
<p className="text-[10px] text-white/60 font-medium uppercase tracking-widest">Human Resource Management System</p>
|
|
@@ -12,7 +12,7 @@ export default function Intro() {
|
|
|
12
12
|
return (
|
|
13
13
|
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-white">
|
|
14
14
|
<div className="flex flex-col items-center gap-6">
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
<div className="flex flex-col items-center">
|
|
17
17
|
<span className="text-4xl font-bold tracking-tight text-zinc-800">
|
|
18
18
|
HR<span className="text-[#008A75]">MS</span>
|
|
@@ -45,7 +45,8 @@ export const positionApi = {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export const reportsApi = {
|
|
48
|
-
employeesOnLeave:
|
|
48
|
+
employeesOnLeave: () => api.get('/api/reports/employees-on-leave').then(r => r.data),
|
|
49
|
+
employeesOnLeaveExcel: () => api.get('/api/reports/employees-on-leave/excel', { responseType: 'blob' }),
|
|
49
50
|
};
|
|
50
51
|
|
|
51
52
|
export default api;
|
|
@@ -22,7 +22,7 @@ export const config = {
|
|
|
22
22
|
// ─── NAVIGATION ───────────────────────────────────────────────────────
|
|
23
23
|
// 'topnav' → horizontal bar at the TOP of the screen
|
|
24
24
|
// 'bottomnav' → bar at the BOTTOM (great for mobile / app-like feel)
|
|
25
|
-
navigation: '
|
|
25
|
+
navigation: 'topnav',
|
|
26
26
|
|
|
27
27
|
// ─── THEME ────────────────────────────────────────────────────────────
|
|
28
28
|
// Pick any theme name from themes.js. Available themes:
|
|
@@ -34,7 +34,7 @@ export const config = {
|
|
|
34
34
|
// 'ocean' (navy/cyan)
|
|
35
35
|
// 'chalk' (black & white)
|
|
36
36
|
// 'violet' (purple)
|
|
37
|
-
theme: '
|
|
37
|
+
theme: 'chalk',
|
|
38
38
|
|
|
39
39
|
// ─── BORDER RADIUS ────────────────────────────────────────────────────
|
|
40
40
|
// Controls how rounded buttons, cards, inputs, and modals are.
|
|
@@ -44,12 +44,12 @@ export const config = {
|
|
|
44
44
|
// 'lg' → very rounded (pill-like)
|
|
45
45
|
// 'xl' → extra rounded
|
|
46
46
|
// 'full' → fully pill-shaped
|
|
47
|
-
rounded: '
|
|
47
|
+
rounded: 'lg',
|
|
48
48
|
|
|
49
49
|
// ─── FONT SIZE ────────────────────────────────────────────────────────
|
|
50
50
|
// 'normal' → default text size
|
|
51
51
|
// 'large' → bigger text (easier to read)
|
|
52
|
-
fontSize: '
|
|
52
|
+
fontSize: 'large',
|
|
53
53
|
|
|
54
54
|
// ─── COLORS ───────────────────────────────────────────────────────────
|
|
55
55
|
// Leave empty ('') to use the theme's default colors.
|
|
@@ -20,7 +20,7 @@ export default function BottomNav() {
|
|
|
20
20
|
<header className="flex-shrink-0 flex items-center h-[52px] px-4 gap-1 z-20"
|
|
21
21
|
style={{ backgroundColor: 'var(--color-nav-bg)', color: 'var(--color-nav-text)', borderBottom: '1px solid var(--color-border)' }}>
|
|
22
22
|
<div className="flex items-center gap-2 mr-4">
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
<div className="flex flex-col mt-4">
|
|
25
25
|
<span className="text-[15px] font-bold tracking-tight leading-tight">HR<span style={{ color: 'var(--color-primary)' }}>MS</span></span>
|
|
26
26
|
<span className="text-[7px] font-medium uppercase tracking-wider leading-tight opacity-60">DAB Enterprise</span>
|
|
@@ -20,7 +20,6 @@ export default function TopNav() {
|
|
|
20
20
|
<header className="flex-shrink-0 flex items-center h-[52px] px-4 gap-1 z-20"
|
|
21
21
|
style={{ backgroundColor: 'var(--color-nav-bg)', color: 'var(--color-nav-text)', borderBottom: '1px solid var(--color-border)' }}>
|
|
22
22
|
<div className="flex items-center gap-2 mr-4 pr-4" style={{ borderRight: '1px solid var(--color-border)' }}>
|
|
23
|
-
<img src="/logo.png" alt="logo" className="w-7 h-9" />
|
|
24
23
|
<div className="flex flex-col mt-4">
|
|
25
24
|
<span className="text-[15px] font-bold tracking-tight leading-tight">HR<span style={{ color: 'var(--color-primary)' }}>MS</span></span>
|
|
26
25
|
<span className="text-[7px] font-medium uppercase tracking-wider leading-tight opacity-60">DAB Enterprise</span>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import { FileText, Users } from 'lucide-react';
|
|
2
|
+
import { Download, FileText, Users } from 'lucide-react';
|
|
3
3
|
import { useToast } from '../components/Toast';
|
|
4
4
|
import { reportsApi } from '../api/ApiClient';
|
|
5
5
|
|
|
@@ -8,6 +8,8 @@ export default function Reports() {
|
|
|
8
8
|
const [report, setReport] = useState(null);
|
|
9
9
|
const [loading, setLoading] = useState(true);
|
|
10
10
|
|
|
11
|
+
const [downloading, setDownloading] = useState(false);
|
|
12
|
+
|
|
11
13
|
const loadReport = useCallback(async () => {
|
|
12
14
|
setLoading(true);
|
|
13
15
|
try {
|
|
@@ -17,6 +19,26 @@ export default function Reports() {
|
|
|
17
19
|
finally { setLoading(false); }
|
|
18
20
|
}, []);
|
|
19
21
|
|
|
22
|
+
const handleDownload = useCallback(async () => {
|
|
23
|
+
setDownloading(true);
|
|
24
|
+
try {
|
|
25
|
+
const response = await reportsApi.employeesOnLeaveExcel();
|
|
26
|
+
const blob = new Blob([response.data], {
|
|
27
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
28
|
+
});
|
|
29
|
+
const url = URL.createObjectURL(blob);
|
|
30
|
+
const a = document.createElement('a');
|
|
31
|
+
a.href = url;
|
|
32
|
+
a.download = `employees-on-leave-report-${new Date().toISOString().slice(0, 10)}.xlsx`;
|
|
33
|
+
document.body.appendChild(a);
|
|
34
|
+
a.click();
|
|
35
|
+
document.body.removeChild(a);
|
|
36
|
+
URL.revokeObjectURL(url);
|
|
37
|
+
toast.success('Report downloaded successfully');
|
|
38
|
+
} catch { toast.error('Failed to download report'); }
|
|
39
|
+
finally { setDownloading(false); }
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
20
42
|
useEffect(() => { loadReport(); }, [loadReport]);
|
|
21
43
|
|
|
22
44
|
const departments = report?.departments ?? {};
|
|
@@ -32,11 +54,21 @@ export default function Reports() {
|
|
|
32
54
|
Employees currently on leave — organised by department
|
|
33
55
|
</p>
|
|
34
56
|
</div>
|
|
35
|
-
<div className="flex items-center gap-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
<div className="flex items-center gap-3">
|
|
58
|
+
<button
|
|
59
|
+
onClick={handleDownload}
|
|
60
|
+
disabled={downloading || total === 0}
|
|
61
|
+
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white text-[13px] font-medium rounded-md hover:bg-green-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
62
|
+
>
|
|
63
|
+
<Download className="w-4 h-4" />
|
|
64
|
+
{downloading ? 'Downloading...' : 'Download Excel'}
|
|
65
|
+
</button>
|
|
66
|
+
<div className="flex items-center gap-2 px-4 py-2 bg-yellow-50 border border-yellow-200 rounded-md">
|
|
67
|
+
<Users className="w-4 h-4 text-yellow-600" />
|
|
68
|
+
<span className="text-[13px] font-semibold text-yellow-800">
|
|
69
|
+
Total on leave: {total}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
40
72
|
</div>
|
|
41
73
|
</div>
|
|
42
74
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alpe-temp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Employee Payroll Management System (EPMS) - Full-stack Express + React app",
|
|
5
5
|
"main": "bin/epms.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,19 +13,7 @@
|
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.0.0"
|
|
15
15
|
},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"bcryptjs": "^2.4.3",
|
|
18
|
-
"connect-mongo": "^6.0.0",
|
|
19
|
-
"cors": "^2.8.5",
|
|
20
|
-
"dotenv": "^16.4.5",
|
|
21
|
-
"exceljs": "^4.4.0",
|
|
22
|
-
"express": "^4.19.2",
|
|
23
|
-
"express-session": "^1.19.0",
|
|
24
|
-
"helmet": "^7.1.0",
|
|
25
|
-
"jsonwebtoken": "^9.0.2",
|
|
26
|
-
"mongoose": "^8.5.0",
|
|
27
|
-
"uuid": "^10.0.0"
|
|
28
|
-
},
|
|
16
|
+
"dependencies": {},
|
|
29
17
|
"repository": {
|
|
30
18
|
"type": "git",
|
|
31
19
|
"url": ""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html,:host{-webkit-text-size-adjust:100%;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder{opacity:1;color:#9ca3af}textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (width>=640px){.container{max-width:640px}}@media (width>=768px){.container{max-width:768px}}@media (width>=1024px){.container{max-width:1024px}}@media (width>=1280px){.container{max-width:1280px}}@media (width>=1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.left-3{left:.75rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-0{top:0}.top-1\/2{top:50%}.top-5{top:1.25rem}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[9999\]{z-index:9999}.col-span-2{grid-column:span 2/span 2}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-auto{margin-bottom:auto}.ml-0\.5{margin-left:.125rem}.ml-auto{margin-left:auto}.mr-4{margin-right:1rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-auto{margin-top:auto}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-20{height:5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[500px\]{height:500px}.h-\[52px\]{height:52px}.h-\[540px\]{height:540px}.h-\[64px\]{height:64px}.h-screen{height:100vh}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-16{width:4rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-5\/12{width:41.6667%}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-fit{width:fit-content}.w-full{width:100%}.min-w-\[150px\]{min-width:150px}.min-w-\[240px\]{min-width:240px}.min-w-max{min-width:max-content}.max-w-2xl{max-width:42rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-\[760px\]{max-width:760px}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate:180deg;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:2s cubic-bezier(.4,0,.6,1) infinite pulse}@keyframes spin{to{transform:rotate(360deg)}}.select-none{-webkit-user-select:none;user-select:none}.resize-none{resize:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-50>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(249 250 251/var(--tw-divide-opacity,1))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-blue-200{--tw-border-opacity:1;border-color:rgb(191 219 254/var(--tw-border-opacity,1))}.border-emerald-200{--tw-border-opacity:1;border-color:rgb(167 243 208/var(--tw-border-opacity,1))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity,1))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.border-white\/20{border-color:#fff3}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity,1))}.border-zinc-200{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.border-t-\[\#008A75\]{--tw-border-opacity:1;border-top-color:rgb(0 138 117/var(--tw-border-opacity,1))}.bg-\[\#008A75\]{--tw-bg-opacity:1;background-color:rgb(0 138 117/var(--tw-bg-opacity,1))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-black\/40{background-color:#0006}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-emerald-50{--tw-bg-opacity:1;background-color:rgb(236 253 245/var(--tw-bg-opacity,1))}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity,1))}.bg-zinc-50{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pl-9{padding-left:2.25rem}.pr-10{padding-right:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-10{padding-top:2.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[22px\]{font-size:22px}.text-\[32px\]{font-size:32px}.text-\[7px\]{font-size:7px}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-\[\#008A75\]{--tw-text-opacity:1;color:rgb(0 138 117/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-600{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/40{color:#fff6}.text-white\/50{color:#ffffff80}.text-white\/60{color:#fff9}.text-yellow-600{--tw-text-opacity:1;color:rgb(202 138 4/var(--tw-text-opacity,1))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity,1))}.text-zinc-800{--tw-text-opacity:1;color:rgb(39 39 42/var(--tw-text-opacity,1))}.text-zinc-800\/60{color:#27272a99}.text-zinc-900{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000), var(--tw-ring-shadow,0 0 #0000), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000), var(--tw-ring-shadow,0 0 #0000), var(--tw-shadow)}.outline{outline-style:solid}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-property:transform;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}:root{--color-primary:#008a75;--color-surface:#f9fafb;--color-card:#fff;--color-border:#e5e7eb;--color-text:#1f2937;--color-text-muted:#9ca3af;--color-nav-bg:#fff;--color-nav-text:#4b5563;--color-nav-active:#008a75;--color-danger:#ef4444;--color-success:#10b981;--color-warning:#f59e0b;--color-info:#3b82f6;--radius:.375rem;--text-xs:.75rem;--text-sm:.8125rem;--text-base:.875rem;--text-lg:1rem;--text-xl:1.25rem;--text-2xl:1.5rem;--text-3xl:2rem}.animate-spin{animation:1s linear infinite spin}.placeholder\:text-gray-400::placeholder{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.hover\:bg-emerald-600:hover{--tw-bg-opacity:1;background-color:rgb(5 150 105/var(--tw-bg-opacity,1))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-gray-900:hover{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity,1))}.hover\:bg-red-600:hover{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-70:hover{opacity:.7}.hover\:opacity-80:hover{opacity:.8}.hover\:opacity-90:hover{opacity:.9}.focus\:outline-none:focus{outline-offset:2px;outline:2px solid #0000}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow,0 0 #0000)}.focus\:ring-\[\#008A75\]:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(0 138 117/var(--tw-ring-opacity,1))}.focus\:ring-\[var\(--color-primary\)\]:focus{--tw-ring-color:var(--color-primary)}.focus\:ring-emerald-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(52 211 153/var(--tw-ring-opacity,1))}.focus\:ring-gray-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(229 231 235/var(--tw-ring-opacity,1))}.focus\:ring-gray-300:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity,1))}.focus\:ring-gray-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(75 85 99/var(--tw-ring-opacity,1))}.focus\:ring-red-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(248 113 113/var(--tw-ring-opacity,1))}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px}.active\:scale-\[0\.98\]:active{--tw-scale-x:.98;--tw-scale-y:.98;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-50:disabled{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.disabled\:text-gray-400:disabled{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=640px){.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:p-10{padding:2.5rem}}@media (width>=1024px){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
|