Spaces:
Running
Running
Commit ·
a0372d5
1
Parent(s): e67c6c0
Create Sample Kandan
Browse files- HR System/index.html +0 -1
- HR System/src/App.css +0 -184
- HR System/src/App.jsx +265 -113
- HR System/src/components/CalendarView.jsx +71 -0
- HR System/src/components/CandidateCard.jsx +39 -0
- HR System/src/components/CandidateDirectory.jsx +130 -0
- HR System/src/components/CandidateModal.jsx +87 -0
- HR System/src/components/EmptyState.jsx +10 -0
- HR System/src/components/FilterBar.jsx +43 -0
- HR System/src/components/Header.jsx +26 -0
- HR System/src/components/KanbanBoard.jsx +46 -0
- HR System/src/components/SettingsPanel.jsx +44 -0
- HR System/src/components/Sidebar.jsx +82 -0
- HR System/src/components/StageColumn.jsx +35 -0
- HR System/src/components/TabsNav.jsx +18 -0
- HR System/src/components/TopActions.jsx +15 -0
- HR System/src/data/dummyCandidates.js +224 -0
- HR System/src/index.css +1 -109
- HR System/src/main.jsx +1 -0
- HR System/src/services/candidateApi.js +44 -0
- HR System/src/styles/global.css +1229 -0
HR System/index.html
CHANGED
|
@@ -2,7 +2,6 @@
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
<title>hr-system</title>
|
| 8 |
</head>
|
|
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
|
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>hr-system</title>
|
| 7 |
</head>
|
HR System/src/App.css
DELETED
|
@@ -1,184 +0,0 @@
|
|
| 1 |
-
.counter {
|
| 2 |
-
font-size: 16px;
|
| 3 |
-
padding: 5px 10px;
|
| 4 |
-
border-radius: 5px;
|
| 5 |
-
color: var(--accent);
|
| 6 |
-
background: var(--accent-bg);
|
| 7 |
-
border: 2px solid transparent;
|
| 8 |
-
transition: border-color 0.3s;
|
| 9 |
-
margin-bottom: 24px;
|
| 10 |
-
|
| 11 |
-
&:hover {
|
| 12 |
-
border-color: var(--accent-border);
|
| 13 |
-
}
|
| 14 |
-
&:focus-visible {
|
| 15 |
-
outline: 2px solid var(--accent);
|
| 16 |
-
outline-offset: 2px;
|
| 17 |
-
}
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
.hero {
|
| 21 |
-
position: relative;
|
| 22 |
-
|
| 23 |
-
.base,
|
| 24 |
-
.framework,
|
| 25 |
-
.vite {
|
| 26 |
-
inset-inline: 0;
|
| 27 |
-
margin: 0 auto;
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
.base {
|
| 31 |
-
width: 170px;
|
| 32 |
-
position: relative;
|
| 33 |
-
z-index: 0;
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
.framework,
|
| 37 |
-
.vite {
|
| 38 |
-
position: absolute;
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
.framework {
|
| 42 |
-
z-index: 1;
|
| 43 |
-
top: 34px;
|
| 44 |
-
height: 28px;
|
| 45 |
-
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
| 46 |
-
scale(1.4);
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
.vite {
|
| 50 |
-
z-index: 0;
|
| 51 |
-
top: 107px;
|
| 52 |
-
height: 26px;
|
| 53 |
-
width: auto;
|
| 54 |
-
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
| 55 |
-
scale(0.8);
|
| 56 |
-
}
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
#center {
|
| 60 |
-
display: flex;
|
| 61 |
-
flex-direction: column;
|
| 62 |
-
gap: 25px;
|
| 63 |
-
place-content: center;
|
| 64 |
-
place-items: center;
|
| 65 |
-
flex-grow: 1;
|
| 66 |
-
|
| 67 |
-
@media (max-width: 1024px) {
|
| 68 |
-
padding: 32px 20px 24px;
|
| 69 |
-
gap: 18px;
|
| 70 |
-
}
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
#next-steps {
|
| 74 |
-
display: flex;
|
| 75 |
-
border-top: 1px solid var(--border);
|
| 76 |
-
text-align: left;
|
| 77 |
-
|
| 78 |
-
& > div {
|
| 79 |
-
flex: 1 1 0;
|
| 80 |
-
padding: 32px;
|
| 81 |
-
@media (max-width: 1024px) {
|
| 82 |
-
padding: 24px 20px;
|
| 83 |
-
}
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
.icon {
|
| 87 |
-
margin-bottom: 16px;
|
| 88 |
-
width: 22px;
|
| 89 |
-
height: 22px;
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
@media (max-width: 1024px) {
|
| 93 |
-
flex-direction: column;
|
| 94 |
-
text-align: center;
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
#docs {
|
| 99 |
-
border-right: 1px solid var(--border);
|
| 100 |
-
|
| 101 |
-
@media (max-width: 1024px) {
|
| 102 |
-
border-right: none;
|
| 103 |
-
border-bottom: 1px solid var(--border);
|
| 104 |
-
}
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
#next-steps ul {
|
| 108 |
-
list-style: none;
|
| 109 |
-
padding: 0;
|
| 110 |
-
display: flex;
|
| 111 |
-
gap: 8px;
|
| 112 |
-
margin: 32px 0 0;
|
| 113 |
-
|
| 114 |
-
.logo {
|
| 115 |
-
height: 18px;
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
a {
|
| 119 |
-
color: var(--text-h);
|
| 120 |
-
font-size: 16px;
|
| 121 |
-
border-radius: 6px;
|
| 122 |
-
background: var(--social-bg);
|
| 123 |
-
display: flex;
|
| 124 |
-
padding: 6px 12px;
|
| 125 |
-
align-items: center;
|
| 126 |
-
gap: 8px;
|
| 127 |
-
text-decoration: none;
|
| 128 |
-
transition: box-shadow 0.3s;
|
| 129 |
-
|
| 130 |
-
&:hover {
|
| 131 |
-
box-shadow: var(--shadow);
|
| 132 |
-
}
|
| 133 |
-
.button-icon {
|
| 134 |
-
height: 18px;
|
| 135 |
-
width: 18px;
|
| 136 |
-
}
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
@media (max-width: 1024px) {
|
| 140 |
-
margin-top: 20px;
|
| 141 |
-
flex-wrap: wrap;
|
| 142 |
-
justify-content: center;
|
| 143 |
-
|
| 144 |
-
li {
|
| 145 |
-
flex: 1 1 calc(50% - 8px);
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
a {
|
| 149 |
-
width: 100%;
|
| 150 |
-
justify-content: center;
|
| 151 |
-
box-sizing: border-box;
|
| 152 |
-
}
|
| 153 |
-
}
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
#spacer {
|
| 157 |
-
height: 88px;
|
| 158 |
-
border-top: 1px solid var(--border);
|
| 159 |
-
@media (max-width: 1024px) {
|
| 160 |
-
height: 48px;
|
| 161 |
-
}
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
.ticks {
|
| 165 |
-
position: relative;
|
| 166 |
-
width: 100%;
|
| 167 |
-
|
| 168 |
-
&::before,
|
| 169 |
-
&::after {
|
| 170 |
-
content: '';
|
| 171 |
-
position: absolute;
|
| 172 |
-
top: -4.5px;
|
| 173 |
-
border: 5px solid transparent;
|
| 174 |
-
}
|
| 175 |
-
|
| 176 |
-
&::before {
|
| 177 |
-
left: 0;
|
| 178 |
-
border-left-color: var(--border);
|
| 179 |
-
}
|
| 180 |
-
&::after {
|
| 181 |
-
right: 0;
|
| 182 |
-
border-right-color: var(--border);
|
| 183 |
-
}
|
| 184 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HR System/src/App.jsx
CHANGED
|
@@ -1,120 +1,272 @@
|
|
| 1 |
-
import { useState } from 'react'
|
| 2 |
-
import
|
| 3 |
-
import
|
| 4 |
-
import
|
| 5 |
-
import './
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
function App() {
|
| 8 |
-
const [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
return (
|
| 11 |
-
<>
|
| 12 |
-
<
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
Discord
|
| 85 |
-
</a>
|
| 86 |
-
</li>
|
| 87 |
-
<li>
|
| 88 |
-
<a href="https://x.com/vite_js" target="_blank">
|
| 89 |
-
<svg
|
| 90 |
-
className="button-icon"
|
| 91 |
-
role="presentation"
|
| 92 |
-
aria-hidden="true"
|
| 93 |
-
>
|
| 94 |
-
<use href="/icons.svg#x-icon"></use>
|
| 95 |
-
</svg>
|
| 96 |
-
X.com
|
| 97 |
-
</a>
|
| 98 |
-
</li>
|
| 99 |
-
<li>
|
| 100 |
-
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
| 101 |
-
<svg
|
| 102 |
-
className="button-icon"
|
| 103 |
-
role="presentation"
|
| 104 |
-
aria-hidden="true"
|
| 105 |
-
>
|
| 106 |
-
<use href="/icons.svg#bluesky-icon"></use>
|
| 107 |
-
</svg>
|
| 108 |
-
Bluesky
|
| 109 |
-
</a>
|
| 110 |
-
</li>
|
| 111 |
-
</ul>
|
| 112 |
-
</div>
|
| 113 |
-
</section>
|
| 114 |
-
|
| 115 |
-
<div className="ticks"></div>
|
| 116 |
-
<section id="spacer"></section>
|
| 117 |
-
</>
|
| 118 |
)
|
| 119 |
}
|
| 120 |
|
|
|
|
| 1 |
+
import { useEffect, useMemo, useState } from 'react'
|
| 2 |
+
import { dummyCandidates, stageOrder } from './data/dummyCandidates'
|
| 3 |
+
import { getCandidates } from './services/candidateApi'
|
| 4 |
+
import Sidebar from './components/Sidebar'
|
| 5 |
+
import Header from './components/Header'
|
| 6 |
+
import FilterBar from './components/FilterBar'
|
| 7 |
+
import KanbanBoard from './components/KanbanBoard'
|
| 8 |
+
import CandidateModal from './components/CandidateModal'
|
| 9 |
+
import TopActions from './components/TopActions'
|
| 10 |
+
import CandidateDirectory from './components/CandidateDirectory'
|
| 11 |
+
import CalendarView from './components/CalendarView'
|
| 12 |
+
import SettingsPanel from './components/SettingsPanel'
|
| 13 |
+
|
| 14 |
+
const initialTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
| 15 |
+
|
| 16 |
+
const viewCopy = {
|
| 17 |
+
Dashboard: {
|
| 18 |
+
description: 'Track the live hiring pipeline.',
|
| 19 |
+
statLabel: 'Visible',
|
| 20 |
+
},
|
| 21 |
+
Candidates: {
|
| 22 |
+
description: 'Search and review every applied candidate.',
|
| 23 |
+
statLabel: 'Matched',
|
| 24 |
+
},
|
| 25 |
+
Calendar: {
|
| 26 |
+
description: 'See the hiring month at a glance.',
|
| 27 |
+
statLabel: 'Month',
|
| 28 |
+
},
|
| 29 |
+
Settings: {
|
| 30 |
+
description: 'Adjust the application appearance.',
|
| 31 |
+
statLabel: 'Theme',
|
| 32 |
+
},
|
| 33 |
+
}
|
| 34 |
|
| 35 |
function App() {
|
| 36 |
+
const [candidates, setCandidates] = useState(dummyCandidates)
|
| 37 |
+
const [selectedCandidate, setSelectedCandidate] = useState(null)
|
| 38 |
+
const [isModalOpen, setIsModalOpen] = useState(false)
|
| 39 |
+
const [activeView, setActiveView] = useState('Dashboard')
|
| 40 |
+
const [searchTerm, setSearchTerm] = useState('')
|
| 41 |
+
const [selectedStage, setSelectedStage] = useState('All')
|
| 42 |
+
const [scoreFilter, setScoreFilter] = useState('All')
|
| 43 |
+
const [candidateSearchTerm, setCandidateSearchTerm] = useState('')
|
| 44 |
+
const [candidateStageFilter, setCandidateStageFilter] = useState('All')
|
| 45 |
+
const [candidatePositionFilter, setCandidatePositionFilter] = useState('All')
|
| 46 |
+
const [theme, setTheme] = useState(initialTheme)
|
| 47 |
+
const [loading, setLoading] = useState(true)
|
| 48 |
+
const [error, setError] = useState('')
|
| 49 |
+
|
| 50 |
+
useEffect(() => {
|
| 51 |
+
document.documentElement.dataset.theme = theme
|
| 52 |
+
window.localStorage.setItem('dashboard-theme', theme)
|
| 53 |
+
}, [theme])
|
| 54 |
+
|
| 55 |
+
useEffect(() => {
|
| 56 |
+
const storedTheme = window.localStorage.getItem('dashboard-theme')
|
| 57 |
+
if (storedTheme === 'light' || storedTheme === 'dark') {
|
| 58 |
+
setTheme(storedTheme)
|
| 59 |
+
}
|
| 60 |
+
}, [])
|
| 61 |
+
|
| 62 |
+
useEffect(() => {
|
| 63 |
+
let active = true
|
| 64 |
+
|
| 65 |
+
async function loadCandidates() {
|
| 66 |
+
try {
|
| 67 |
+
setLoading(true)
|
| 68 |
+
const data = await getCandidates()
|
| 69 |
+
if (active) {
|
| 70 |
+
setCandidates(data)
|
| 71 |
+
setError('')
|
| 72 |
+
}
|
| 73 |
+
} catch (loadError) {
|
| 74 |
+
if (active) {
|
| 75 |
+
setError(loadError instanceof Error ? loadError.message : 'Failed to load candidates')
|
| 76 |
+
}
|
| 77 |
+
} finally {
|
| 78 |
+
if (active) {
|
| 79 |
+
setLoading(false)
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
loadCandidates()
|
| 85 |
+
|
| 86 |
+
return () => {
|
| 87 |
+
active = false
|
| 88 |
+
}
|
| 89 |
+
}, [])
|
| 90 |
+
|
| 91 |
+
const filteredCandidates = useMemo(() => {
|
| 92 |
+
return candidates.filter((candidate) => {
|
| 93 |
+
const matchesSearch = candidate.name.toLowerCase().includes(searchTerm.toLowerCase())
|
| 94 |
+
const matchesStage = selectedStage === 'All' ? true : candidate.stage === selectedStage
|
| 95 |
+
|
| 96 |
+
const matchesScore =
|
| 97 |
+
scoreFilter === 'All'
|
| 98 |
+
? true
|
| 99 |
+
: scoreFilter === '4+'
|
| 100 |
+
? candidate.overallScore >= 4
|
| 101 |
+
: scoreFilter === '3-4'
|
| 102 |
+
? candidate.overallScore >= 3 && candidate.overallScore < 4
|
| 103 |
+
: candidate.overallScore > 0 && candidate.overallScore < 3
|
| 104 |
+
|
| 105 |
+
return matchesSearch && matchesStage && matchesScore
|
| 106 |
+
})
|
| 107 |
+
}, [candidates, searchTerm, selectedStage, scoreFilter])
|
| 108 |
+
|
| 109 |
+
const candidatePositions = useMemo(() => {
|
| 110 |
+
return Array.from(
|
| 111 |
+
new Set(
|
| 112 |
+
candidates
|
| 113 |
+
.map((candidate) => candidate.appliedPosition ?? candidate.role)
|
| 114 |
+
.filter(Boolean),
|
| 115 |
+
),
|
| 116 |
+
).sort((left, right) => left.localeCompare(right))
|
| 117 |
+
}, [candidates])
|
| 118 |
+
|
| 119 |
+
const filteredDirectoryCandidates = useMemo(() => {
|
| 120 |
+
return candidates.filter((candidate) => {
|
| 121 |
+
const appliedPosition = candidate.appliedPosition ?? candidate.role
|
| 122 |
+
const searchValue = candidateSearchTerm.toLowerCase()
|
| 123 |
+
const matchesSearch =
|
| 124 |
+
candidate.name.toLowerCase().includes(searchValue) ||
|
| 125 |
+
candidate.email.toLowerCase().includes(searchValue) ||
|
| 126 |
+
candidate.phone.toLowerCase().includes(searchValue) ||
|
| 127 |
+
appliedPosition.toLowerCase().includes(searchValue)
|
| 128 |
+
const matchesStage = candidateStageFilter === 'All' ? true : candidate.stage === candidateStageFilter
|
| 129 |
+
const matchesPosition =
|
| 130 |
+
candidatePositionFilter === 'All' ? true : appliedPosition === candidatePositionFilter
|
| 131 |
+
|
| 132 |
+
return matchesSearch && matchesStage && matchesPosition
|
| 133 |
+
})
|
| 134 |
+
}, [candidates, candidateSearchTerm, candidateStageFilter, candidatePositionFilter])
|
| 135 |
+
|
| 136 |
+
const currentViewCopy = viewCopy[activeView]
|
| 137 |
+
const activeStatValue =
|
| 138 |
+
activeView === 'Dashboard'
|
| 139 |
+
? `${filteredCandidates.length} candidates`
|
| 140 |
+
: activeView === 'Candidates'
|
| 141 |
+
? `${filteredDirectoryCandidates.length} profiles`
|
| 142 |
+
: activeView === 'Calendar'
|
| 143 |
+
? new Date().toLocaleString('en-US', { month: 'long', year: 'numeric' })
|
| 144 |
+
: theme === 'dark'
|
| 145 |
+
? 'Dark mode'
|
| 146 |
+
: 'Light mode'
|
| 147 |
+
|
| 148 |
+
const handleOpenCandidate = (candidate) => {
|
| 149 |
+
setSelectedCandidate(candidate)
|
| 150 |
+
setIsModalOpen(true)
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
const handleSaveCandidate = (updatedCandidate) => {
|
| 154 |
+
setCandidates((currentCandidates) =>
|
| 155 |
+
currentCandidates.map((candidate) =>
|
| 156 |
+
candidate.id === updatedCandidate.id ? updatedCandidate : candidate,
|
| 157 |
+
),
|
| 158 |
+
)
|
| 159 |
+
setSelectedCandidate(updatedCandidate)
|
| 160 |
+
setIsModalOpen(true)
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
const handleCreateCandidate = () => {
|
| 164 |
+
const nextCandidate = {
|
| 165 |
+
id: `cand-${String(candidates.length + 1).padStart(3, '0')}`,
|
| 166 |
+
name: 'New Candidate',
|
| 167 |
+
role: 'Research and Development Officer',
|
| 168 |
+
appliedPosition: 'Research and Development Officer',
|
| 169 |
+
stage: 'Applied',
|
| 170 |
+
applicationDate: new Date().toISOString().slice(0, 10),
|
| 171 |
+
overallScore: 0,
|
| 172 |
+
referred: false,
|
| 173 |
+
assessmentAdded: false,
|
| 174 |
+
email: 'new.candidate@example.com',
|
| 175 |
+
phone: '+94 70 000 0000',
|
| 176 |
+
location: 'Colombo, Sri Lanka',
|
| 177 |
+
notes: 'New placeholder candidate for frontend-only workflow.',
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
setCandidates((currentCandidates) => [nextCandidate, ...currentCandidates])
|
| 181 |
+
handleOpenCandidate(nextCandidate)
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
const handleResetFilters = () => {
|
| 185 |
+
setSearchTerm('')
|
| 186 |
+
setSelectedStage('All')
|
| 187 |
+
setScoreFilter('All')
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
const handleResetCandidateFilters = () => {
|
| 191 |
+
setCandidateSearchTerm('')
|
| 192 |
+
setCandidateStageFilter('All')
|
| 193 |
+
setCandidatePositionFilter('All')
|
| 194 |
+
}
|
| 195 |
|
| 196 |
return (
|
| 197 |
+
<div className="app-shell">
|
| 198 |
+
<Sidebar activeView={activeView} onSelectView={setActiveView} />
|
| 199 |
+
|
| 200 |
+
<main className="workspace">
|
| 201 |
+
<Header
|
| 202 |
+
title={activeView}
|
| 203 |
+
description={currentViewCopy.description}
|
| 204 |
+
statLabel={currentViewCopy.statLabel}
|
| 205 |
+
statValue={activeStatValue}
|
| 206 |
+
/>
|
| 207 |
+
|
| 208 |
+
{activeView === 'Dashboard' ? (
|
| 209 |
+
<>
|
| 210 |
+
<div className="workspace__toolbar">
|
| 211 |
+
<FilterBar
|
| 212 |
+
searchTerm={searchTerm}
|
| 213 |
+
onSearchTermChange={setSearchTerm}
|
| 214 |
+
stageFilter={selectedStage}
|
| 215 |
+
onStageFilterChange={setSelectedStage}
|
| 216 |
+
scoreFilter={scoreFilter}
|
| 217 |
+
onScoreFilterChange={setScoreFilter}
|
| 218 |
+
stages={stageOrder}
|
| 219 |
+
onResetFilters={handleResetFilters}
|
| 220 |
+
/>
|
| 221 |
+
<TopActions selectedCount={filteredCandidates.length} onCreateCandidate={handleCreateCandidate} />
|
| 222 |
+
</div>
|
| 223 |
+
|
| 224 |
+
{error ? <div className="banner banner--error">{error}</div> : null}
|
| 225 |
+
{loading ? <div className="banner">Loading candidates from mock data...</div> : null}
|
| 226 |
+
|
| 227 |
+
<KanbanBoard
|
| 228 |
+
candidates={candidates}
|
| 229 |
+
searchTerm={searchTerm}
|
| 230 |
+
selectedStage={selectedStage}
|
| 231 |
+
scoreFilter={scoreFilter}
|
| 232 |
+
onSelectCandidate={handleOpenCandidate}
|
| 233 |
+
/>
|
| 234 |
+
</>
|
| 235 |
+
) : null}
|
| 236 |
+
|
| 237 |
+
{activeView === 'Candidates' ? (
|
| 238 |
+
<CandidateDirectory
|
| 239 |
+
candidates={filteredDirectoryCandidates}
|
| 240 |
+
searchTerm={candidateSearchTerm}
|
| 241 |
+
onSearchTermChange={setCandidateSearchTerm}
|
| 242 |
+
stageFilter={candidateStageFilter}
|
| 243 |
+
onStageFilterChange={setCandidateStageFilter}
|
| 244 |
+
positionFilter={candidatePositionFilter}
|
| 245 |
+
onPositionFilterChange={setCandidatePositionFilter}
|
| 246 |
+
positions={candidatePositions}
|
| 247 |
+
onResetFilters={handleResetCandidateFilters}
|
| 248 |
+
onSelectCandidate={handleOpenCandidate}
|
| 249 |
+
/>
|
| 250 |
+
) : null}
|
| 251 |
+
|
| 252 |
+
{activeView === 'Calendar' ? <CalendarView /> : null}
|
| 253 |
+
|
| 254 |
+
{activeView === 'Settings' ? (
|
| 255 |
+
<SettingsPanel
|
| 256 |
+
theme={theme}
|
| 257 |
+
onToggleTheme={() => setTheme((currentTheme) => (currentTheme === 'dark' ? 'light' : 'dark'))}
|
| 258 |
+
/>
|
| 259 |
+
) : null}
|
| 260 |
+
</main>
|
| 261 |
+
|
| 262 |
+
<CandidateModal
|
| 263 |
+
candidate={selectedCandidate}
|
| 264 |
+
isOpen={isModalOpen}
|
| 265 |
+
onClose={() => setIsModalOpen(false)}
|
| 266 |
+
onSave={handleSaveCandidate}
|
| 267 |
+
stageOrder={stageOrder}
|
| 268 |
+
/>
|
| 269 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
)
|
| 271 |
}
|
| 272 |
|
HR System/src/components/CalendarView.jsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function CalendarView() {
|
| 2 |
+
const today = new Date()
|
| 3 |
+
const year = today.getFullYear()
|
| 4 |
+
const month = today.getMonth()
|
| 5 |
+
const monthLabel = today.toLocaleString('en-US', { month: 'long', year: 'numeric' })
|
| 6 |
+
const firstDay = new Date(year, month, 1)
|
| 7 |
+
const lastDay = new Date(year, month + 1, 0)
|
| 8 |
+
const leadingDays = firstDay.getDay()
|
| 9 |
+
const totalDays = lastDay.getDate()
|
| 10 |
+
const previousMonthTotal = new Date(year, month, 0).getDate()
|
| 11 |
+
|
| 12 |
+
const days = Array.from({ length: 42 }, (_, index) => {
|
| 13 |
+
const dayNumber = index - leadingDays + 1
|
| 14 |
+
const isPreviousMonth = dayNumber < 1
|
| 15 |
+
const isCurrentMonth = dayNumber >= 1 && dayNumber <= totalDays
|
| 16 |
+
const isNextMonth = dayNumber > totalDays
|
| 17 |
+
const displayDay = isPreviousMonth
|
| 18 |
+
? previousMonthTotal + dayNumber
|
| 19 |
+
: isNextMonth
|
| 20 |
+
? dayNumber - totalDays
|
| 21 |
+
: dayNumber
|
| 22 |
+
|
| 23 |
+
const date = isCurrentMonth ? new Date(year, month, dayNumber) : null
|
| 24 |
+
const isToday =
|
| 25 |
+
date &&
|
| 26 |
+
date.getDate() === today.getDate() &&
|
| 27 |
+
date.getMonth() === today.getMonth() &&
|
| 28 |
+
date.getFullYear() === today.getFullYear()
|
| 29 |
+
|
| 30 |
+
return {
|
| 31 |
+
displayDay,
|
| 32 |
+
isCurrentMonth,
|
| 33 |
+
isToday,
|
| 34 |
+
}
|
| 35 |
+
})
|
| 36 |
+
|
| 37 |
+
return (
|
| 38 |
+
<section className="calendar-view" aria-label="Monthly calendar">
|
| 39 |
+
<div className="calendar-view__header">
|
| 40 |
+
<div>
|
| 41 |
+
<p className="calendar-view__eyebrow">Calendar</p>
|
| 42 |
+
<h2>{monthLabel}</h2>
|
| 43 |
+
<p>Static month view for dates only. No day cells are clickable.</p>
|
| 44 |
+
</div>
|
| 45 |
+
<div className="calendar-view__badge">
|
| 46 |
+
<strong>{totalDays}</strong>
|
| 47 |
+
<span>days in month</span>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<div className="calendar-grid" role="presentation">
|
| 52 |
+
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((label) => (
|
| 53 |
+
<div key={label} className="calendar-grid__weekday">
|
| 54 |
+
{label}
|
| 55 |
+
</div>
|
| 56 |
+
))}
|
| 57 |
+
|
| 58 |
+
{days.map((day, index) => (
|
| 59 |
+
<div
|
| 60 |
+
key={`${day.displayDay}-${index}`}
|
| 61 |
+
className={`calendar-grid__day${day.isCurrentMonth ? '' : ' is-muted'}${day.isToday ? ' is-today' : ''}`}
|
| 62 |
+
>
|
| 63 |
+
<span>{day.displayDay}</span>
|
| 64 |
+
</div>
|
| 65 |
+
))}
|
| 66 |
+
</div>
|
| 67 |
+
</section>
|
| 68 |
+
)
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
export default CalendarView
|
HR System/src/components/CandidateCard.jsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function CandidateCard({ candidate, onClick }) {
|
| 2 |
+
const scoreLabel = candidate.overallScore > 0 ? `${candidate.overallScore.toFixed(1)} Overall` : 'No score yet'
|
| 3 |
+
|
| 4 |
+
return (
|
| 5 |
+
<button type="button" className="candidate-card" onClick={onClick}>
|
| 6 |
+
<div className="candidate-card__top">
|
| 7 |
+
<div className={`avatar avatar--${candidate.id.slice(-1)}`} aria-hidden="true">
|
| 8 |
+
{candidate.name
|
| 9 |
+
.split(' ')
|
| 10 |
+
.map((part) => part.charAt(0))
|
| 11 |
+
.slice(0, 2)
|
| 12 |
+
.join('')}
|
| 13 |
+
</div>
|
| 14 |
+
<div className="candidate-card__heading">
|
| 15 |
+
<strong>{candidate.name}</strong>
|
| 16 |
+
<span>{candidate.applicationDate}</span>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
|
| 20 |
+
<div className="candidate-card__body">
|
| 21 |
+
<span className="pill pill--score">★ {scoreLabel}</span>
|
| 22 |
+
{candidate.referred ? <span className="pill pill--muted">Referred</span> : null}
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<div className="candidate-card__footer">
|
| 26 |
+
{candidate.assessmentAdded ? (
|
| 27 |
+
<span className="candidate-card__action">Assessment added</span>
|
| 28 |
+
) : (
|
| 29 |
+
<span className="candidate-card__action candidate-card__action--accent">Add assessment</span>
|
| 30 |
+
)}
|
| 31 |
+
<span className="candidate-card__chevron" aria-hidden="true">
|
| 32 |
+
›
|
| 33 |
+
</span>
|
| 34 |
+
</div>
|
| 35 |
+
</button>
|
| 36 |
+
)
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
export default CandidateCard
|
HR System/src/components/CandidateDirectory.jsx
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import EmptyState from './EmptyState'
|
| 2 |
+
import { stageOrder } from '../data/dummyCandidates'
|
| 3 |
+
|
| 4 |
+
function CandidateDirectory({
|
| 5 |
+
candidates,
|
| 6 |
+
searchTerm,
|
| 7 |
+
onSearchTermChange,
|
| 8 |
+
stageFilter,
|
| 9 |
+
onStageFilterChange,
|
| 10 |
+
positionFilter,
|
| 11 |
+
onPositionFilterChange,
|
| 12 |
+
positions,
|
| 13 |
+
onResetFilters,
|
| 14 |
+
onSelectCandidate,
|
| 15 |
+
}) {
|
| 16 |
+
return (
|
| 17 |
+
<section className="directory" aria-label="Candidate directory">
|
| 18 |
+
<div className="directory__header">
|
| 19 |
+
<div>
|
| 20 |
+
<p className="directory__eyebrow">Candidate directory</p>
|
| 21 |
+
<h2>Applied candidates</h2>
|
| 22 |
+
<p>Search, filter, and review every applicant in one place.</p>
|
| 23 |
+
</div>
|
| 24 |
+
<div className="directory__count">
|
| 25 |
+
<strong>{candidates.length}</strong>
|
| 26 |
+
<span>matches</span>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<div className="directory__filters">
|
| 31 |
+
<label className="field field--search">
|
| 32 |
+
<span className="sr-only">Search candidates</span>
|
| 33 |
+
<input
|
| 34 |
+
type="search"
|
| 35 |
+
placeholder="Search candidates, email, phone, or position"
|
| 36 |
+
value={searchTerm}
|
| 37 |
+
onChange={(event) => onSearchTermChange(event.target.value)}
|
| 38 |
+
/>
|
| 39 |
+
</label>
|
| 40 |
+
|
| 41 |
+
<label className="field">
|
| 42 |
+
<span className="field__label">Stage</span>
|
| 43 |
+
<select value={stageFilter} onChange={(event) => onStageFilterChange(event.target.value)}>
|
| 44 |
+
<option value="All">All stages</option>
|
| 45 |
+
{stageOrder.map((stage) => (
|
| 46 |
+
<option key={stage} value={stage}>
|
| 47 |
+
{stage}
|
| 48 |
+
</option>
|
| 49 |
+
))}
|
| 50 |
+
</select>
|
| 51 |
+
</label>
|
| 52 |
+
|
| 53 |
+
<label className="field">
|
| 54 |
+
<span className="field__label">Applied position</span>
|
| 55 |
+
<select value={positionFilter} onChange={(event) => onPositionFilterChange(event.target.value)}>
|
| 56 |
+
<option value="All">All positions</option>
|
| 57 |
+
{positions.map((position) => (
|
| 58 |
+
<option key={position} value={position}>
|
| 59 |
+
{position}
|
| 60 |
+
</option>
|
| 61 |
+
))}
|
| 62 |
+
</select>
|
| 63 |
+
</label>
|
| 64 |
+
|
| 65 |
+
<button type="button" className="ghost-button" onClick={onResetFilters}>
|
| 66 |
+
Reset filters
|
| 67 |
+
</button>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
{candidates.length ? (
|
| 71 |
+
<div className="directory__list">
|
| 72 |
+
<div className="directory__list-head" aria-hidden="true">
|
| 73 |
+
<span>Candidate</span>
|
| 74 |
+
<span>Contact</span>
|
| 75 |
+
<span>Applied position</span>
|
| 76 |
+
<span>Stage</span>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
{candidates.map((candidate) => {
|
| 80 |
+
const appliedPosition = candidate.appliedPosition ?? candidate.role
|
| 81 |
+
const initials = candidate.name
|
| 82 |
+
.split(' ')
|
| 83 |
+
.map((part) => part.charAt(0))
|
| 84 |
+
.slice(0, 2)
|
| 85 |
+
.join('')
|
| 86 |
+
|
| 87 |
+
return (
|
| 88 |
+
<button
|
| 89 |
+
key={candidate.id}
|
| 90 |
+
type="button"
|
| 91 |
+
className="candidate-row"
|
| 92 |
+
onClick={() => onSelectCandidate(candidate)}
|
| 93 |
+
>
|
| 94 |
+
<div className="candidate-row__identity">
|
| 95 |
+
<div className={`avatar avatar--${candidate.id.slice(-1)}`} aria-hidden="true">
|
| 96 |
+
{initials}
|
| 97 |
+
</div>
|
| 98 |
+
<div className="candidate-row__nameblock">
|
| 99 |
+
<strong>{candidate.name}</strong>
|
| 100 |
+
<span>{candidate.applicationDate}</span>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<div className="candidate-row__contact">
|
| 105 |
+
<span>{candidate.phone}</span>
|
| 106 |
+
<span>{candidate.email}</span>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
+
<div className="candidate-row__position">
|
| 110 |
+
<span>{appliedPosition}</span>
|
| 111 |
+
</div>
|
| 112 |
+
|
| 113 |
+
<div className="candidate-row__stage">
|
| 114 |
+
<span className="pill pill--score">{candidate.stage}</span>
|
| 115 |
+
</div>
|
| 116 |
+
</button>
|
| 117 |
+
)
|
| 118 |
+
})}
|
| 119 |
+
</div>
|
| 120 |
+
) : (
|
| 121 |
+
<EmptyState
|
| 122 |
+
title="No matching candidates"
|
| 123 |
+
description="Try widening the search, stage, or applied position filters."
|
| 124 |
+
/>
|
| 125 |
+
)}
|
| 126 |
+
</section>
|
| 127 |
+
)
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
export default CandidateDirectory
|
HR System/src/components/CandidateModal.jsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function CandidateModal({ candidate, isOpen, onClose, onSave, stageOrder }) {
|
| 2 |
+
if (!isOpen || !candidate) {
|
| 3 |
+
return null
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
return (
|
| 7 |
+
<div className="modal-backdrop" role="presentation" onClick={onClose}>
|
| 8 |
+
<div
|
| 9 |
+
className="modal"
|
| 10 |
+
role="dialog"
|
| 11 |
+
aria-modal="true"
|
| 12 |
+
aria-labelledby="candidate-modal-title"
|
| 13 |
+
onClick={(event) => event.stopPropagation()}
|
| 14 |
+
>
|
| 15 |
+
<div className="modal__header">
|
| 16 |
+
<div>
|
| 17 |
+
<p className="modal__eyebrow">Candidate details</p>
|
| 18 |
+
<h2 id="candidate-modal-title">{candidate.name}</h2>
|
| 19 |
+
</div>
|
| 20 |
+
<button type="button" className="ghost-button ghost-button--inline" onClick={onClose}>
|
| 21 |
+
Close
|
| 22 |
+
</button>
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<div className="modal__content">
|
| 26 |
+
<section className="modal__card">
|
| 27 |
+
<div className={`avatar avatar--${candidate.id.slice(-1)} avatar--large`} aria-hidden="true">
|
| 28 |
+
{candidate.name
|
| 29 |
+
.split(' ')
|
| 30 |
+
.map((part) => part.charAt(0))
|
| 31 |
+
.slice(0, 2)
|
| 32 |
+
.join('')}
|
| 33 |
+
</div>
|
| 34 |
+
<div className="modal__facts">
|
| 35 |
+
<p><strong>Applied Position:</strong> {candidate.appliedPosition ?? candidate.role}</p>
|
| 36 |
+
<p><strong>Email:</strong> {candidate.email}</p>
|
| 37 |
+
<p><strong>Phone:</strong> {candidate.phone}</p>
|
| 38 |
+
<p><strong>Location:</strong> {candidate.location}</p>
|
| 39 |
+
</div>
|
| 40 |
+
</section>
|
| 41 |
+
|
| 42 |
+
<section className="modal__card">
|
| 43 |
+
<label className="field field--stacked">
|
| 44 |
+
<span className="field__label">Stage</span>
|
| 45 |
+
<select
|
| 46 |
+
value={candidate.stage}
|
| 47 |
+
onChange={(event) => onSave({ ...candidate, stage: event.target.value })}
|
| 48 |
+
>
|
| 49 |
+
{stageOrder.map((stage) => (
|
| 50 |
+
<option key={stage} value={stage}>
|
| 51 |
+
{stage}
|
| 52 |
+
</option>
|
| 53 |
+
))}
|
| 54 |
+
</select>
|
| 55 |
+
</label>
|
| 56 |
+
|
| 57 |
+
<div className="modal__grid">
|
| 58 |
+
<div>
|
| 59 |
+
<span className="modal__stat-label">Applied</span>
|
| 60 |
+
<strong>{candidate.applicationDate}</strong>
|
| 61 |
+
</div>
|
| 62 |
+
<div>
|
| 63 |
+
<span className="modal__stat-label">Score</span>
|
| 64 |
+
<strong>{candidate.overallScore > 0 ? candidate.overallScore.toFixed(1) : 'N/A'}</strong>
|
| 65 |
+
</div>
|
| 66 |
+
<div>
|
| 67 |
+
<span className="modal__stat-label">Referral</span>
|
| 68 |
+
<strong>{candidate.referred ? 'Yes' : 'No'}</strong>
|
| 69 |
+
</div>
|
| 70 |
+
<div>
|
| 71 |
+
<span className="modal__stat-label">Assessment</span>
|
| 72 |
+
<strong>{candidate.assessmentAdded ? 'Added' : 'Pending'}</strong>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
</section>
|
| 76 |
+
|
| 77 |
+
<section className="modal__card modal__card--full">
|
| 78 |
+
<span className="modal__stat-label">Notes</span>
|
| 79 |
+
<p>{candidate.notes}</p>
|
| 80 |
+
</section>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
)
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
export default CandidateModal
|
HR System/src/components/EmptyState.jsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function EmptyState({ title, description }) {
|
| 2 |
+
return (
|
| 3 |
+
<div className="empty-state">
|
| 4 |
+
<strong>{title}</strong>
|
| 5 |
+
<p>{description}</p>
|
| 6 |
+
</div>
|
| 7 |
+
)
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export default EmptyState
|
HR System/src/components/FilterBar.jsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function FilterBar({ searchTerm, onSearchTermChange, stageFilter, onStageFilterChange, scoreFilter, onScoreFilterChange, stages, onResetFilters }) {
|
| 2 |
+
return (
|
| 3 |
+
<section className="filter-bar" aria-label="Search and filters">
|
| 4 |
+
<label className="field field--search">
|
| 5 |
+
<span className="sr-only">Search candidates</span>
|
| 6 |
+
<input
|
| 7 |
+
type="search"
|
| 8 |
+
placeholder="Search candidates"
|
| 9 |
+
value={searchTerm}
|
| 10 |
+
onChange={(event) => onSearchTermChange(event.target.value)}
|
| 11 |
+
/>
|
| 12 |
+
</label>
|
| 13 |
+
|
| 14 |
+
<label className="field">
|
| 15 |
+
<span className="field__label">Stage</span>
|
| 16 |
+
<select value={stageFilter} onChange={(event) => onStageFilterChange(event.target.value)}>
|
| 17 |
+
<option value="All">All stages</option>
|
| 18 |
+
{stages.map((stage) => (
|
| 19 |
+
<option key={stage} value={stage}>
|
| 20 |
+
{stage}
|
| 21 |
+
</option>
|
| 22 |
+
))}
|
| 23 |
+
</select>
|
| 24 |
+
</label>
|
| 25 |
+
|
| 26 |
+
<label className="field">
|
| 27 |
+
<span className="field__label">Score</span>
|
| 28 |
+
<select value={scoreFilter} onChange={(event) => onScoreFilterChange(event.target.value)}>
|
| 29 |
+
<option value="All">All scores</option>
|
| 30 |
+
<option value="4+">4.0 and above</option>
|
| 31 |
+
<option value="3-4">3.0 to 3.9</option>
|
| 32 |
+
<option value="Below 3">Below 3.0</option>
|
| 33 |
+
</select>
|
| 34 |
+
</label>
|
| 35 |
+
|
| 36 |
+
<button type="button" className="ghost-button" onClick={onResetFilters}>
|
| 37 |
+
Reset filters
|
| 38 |
+
</button>
|
| 39 |
+
</section>
|
| 40 |
+
)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
export default FilterBar
|
HR System/src/components/Header.jsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function Header({ title, description, statLabel, statValue }) {
|
| 2 |
+
return (
|
| 3 |
+
<header className="header">
|
| 4 |
+
<div className="header__copy">
|
| 5 |
+
<p className="header__eyebrow">HR workspace</p>
|
| 6 |
+
<h1 className="header__title">{title}</h1>
|
| 7 |
+
<div className="header__meta">
|
| 8 |
+
<span>{description}</span>
|
| 9 |
+
<span className="header__dot" aria-hidden="true" />
|
| 10 |
+
<span>
|
| 11 |
+
{statLabel}: {statValue}
|
| 12 |
+
</span>
|
| 13 |
+
</div>
|
| 14 |
+
</div>
|
| 15 |
+
|
| 16 |
+
<div className="header__actions">
|
| 17 |
+
<div className="header__crumb">
|
| 18 |
+
<span className="header__crumb-label">Active view</span>
|
| 19 |
+
<strong>{title}</strong>
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
</header>
|
| 23 |
+
)
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
export default Header
|
HR System/src/components/KanbanBoard.jsx
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { stageOrder } from '../data/dummyCandidates'
|
| 2 |
+
import StageColumn from './StageColumn'
|
| 3 |
+
|
| 4 |
+
function KanbanBoard({ candidates, searchTerm, selectedStage, scoreFilter, onSelectCandidate }) {
|
| 5 |
+
const matchesScoreFilter = (candidate) => {
|
| 6 |
+
if (scoreFilter === 'All') {
|
| 7 |
+
return true
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
if (scoreFilter === '4+') {
|
| 11 |
+
return candidate.overallScore >= 4
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
if (scoreFilter === '3-4') {
|
| 15 |
+
return candidate.overallScore >= 3 && candidate.overallScore < 4
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
return candidate.overallScore > 0 && candidate.overallScore < 3
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const filteredCandidates = candidates.filter((candidate) => {
|
| 22 |
+
const matchesSearch = candidate.name.toLowerCase().includes(searchTerm.toLowerCase())
|
| 23 |
+
const matchesStage = selectedStage === 'All' ? true : candidate.stage === selectedStage
|
| 24 |
+
return matchesSearch && matchesStage && matchesScoreFilter(candidate)
|
| 25 |
+
})
|
| 26 |
+
|
| 27 |
+
return (
|
| 28 |
+
<section className="board-wrap" aria-label="Candidate board">
|
| 29 |
+
<div className="kanban-board">
|
| 30 |
+
{stageOrder.map((stage) => {
|
| 31 |
+
const stageCandidates = filteredCandidates.filter((candidate) => candidate.stage === stage)
|
| 32 |
+
return (
|
| 33 |
+
<StageColumn
|
| 34 |
+
key={stage}
|
| 35 |
+
stage={{ name: stage }}
|
| 36 |
+
candidates={stageCandidates}
|
| 37 |
+
onSelectCandidate={onSelectCandidate}
|
| 38 |
+
/>
|
| 39 |
+
)
|
| 40 |
+
})}
|
| 41 |
+
</div>
|
| 42 |
+
</section>
|
| 43 |
+
)
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
export default KanbanBoard
|
HR System/src/components/SettingsPanel.jsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function SettingsPanel({ theme, onToggleTheme }) {
|
| 2 |
+
const isDark = theme === 'dark'
|
| 3 |
+
|
| 4 |
+
return (
|
| 5 |
+
<section className="settings-panel" aria-label="Settings">
|
| 6 |
+
<div className="settings-panel__header">
|
| 7 |
+
<div>
|
| 8 |
+
<p className="settings-panel__eyebrow">Settings</p>
|
| 9 |
+
<h2>Appearance</h2>
|
| 10 |
+
<p>Theme switching lives here only, as requested.</p>
|
| 11 |
+
</div>
|
| 12 |
+
</div>
|
| 13 |
+
|
| 14 |
+
<div className="settings-panel__card">
|
| 15 |
+
<div className="settings-panel__copy">
|
| 16 |
+
<span className="settings-panel__label">Light mode / Dark mode</span>
|
| 17 |
+
<strong>{isDark ? 'Dark mode is active' : 'Light mode is active'}</strong>
|
| 18 |
+
<p>
|
| 19 |
+
Use the toggle to switch the whole app theme. The rest of the interface does not show
|
| 20 |
+
any other theme control.
|
| 21 |
+
</p>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<button
|
| 25 |
+
type="button"
|
| 26 |
+
className={`theme-switch${isDark ? ' is-dark' : ''}`}
|
| 27 |
+
onClick={onToggleTheme}
|
| 28 |
+
aria-pressed={isDark}
|
| 29 |
+
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
| 30 |
+
>
|
| 31 |
+
<span className="theme-switch__track" aria-hidden="true">
|
| 32 |
+
<span className="theme-switch__thumb" />
|
| 33 |
+
</span>
|
| 34 |
+
<span className="theme-switch__labels">
|
| 35 |
+
<span>Light mode</span>
|
| 36 |
+
<span>Dark mode</span>
|
| 37 |
+
</span>
|
| 38 |
+
</button>
|
| 39 |
+
</div>
|
| 40 |
+
</section>
|
| 41 |
+
)
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
export default SettingsPanel
|
HR System/src/components/Sidebar.jsx
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const navigationItems = [
|
| 2 |
+
{
|
| 3 |
+
label: 'Dashboard',
|
| 4 |
+
view: 'Dashboard',
|
| 5 |
+
icon: (
|
| 6 |
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
| 7 |
+
<rect x="3" y="3" width="8" height="8" rx="2" fill="currentColor" />
|
| 8 |
+
<rect x="13" y="3" width="8" height="5" rx="2" fill="currentColor" opacity="0.85" />
|
| 9 |
+
<rect x="13" y="10" width="8" height="11" rx="2" fill="currentColor" opacity="0.72" />
|
| 10 |
+
<rect x="3" y="13" width="8" height="8" rx="2" fill="currentColor" opacity="0.9" />
|
| 11 |
+
</svg>
|
| 12 |
+
),
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
label: 'Candidates',
|
| 16 |
+
view: 'Candidates',
|
| 17 |
+
icon: (
|
| 18 |
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
| 19 |
+
<path
|
| 20 |
+
d="M16.5 12.5c1.93 0 3.5-1.57 3.5-3.5S18.43 5.5 16.5 5.5 13 7.07 13 9s1.57 3.5 3.5 3.5ZM7.5 11.5A3.5 3.5 0 1 0 7.5 4.5a3.5 3.5 0 0 0 0 7Zm9 2c-2.67 0-8 1.34-8 4v1.5h16V17.5c0-2.66-5.33-4-8-4Zm-9 0c-.92 0-2.15.17-3.25.5C2.1 14.4 1 15.39 1 16.75V20h4.5v-1.5c0-1.1.34-2.04.96-2.82.26-.32.56-.62.89-.88-.24-.17-.42-.3-.65-.3Z"
|
| 21 |
+
fill="currentColor"
|
| 22 |
+
/>
|
| 23 |
+
</svg>
|
| 24 |
+
),
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
label: 'Calendar',
|
| 28 |
+
view: 'Calendar',
|
| 29 |
+
icon: (
|
| 30 |
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
| 31 |
+
<rect x="3" y="5" width="18" height="16" rx="3" fill="none" stroke="currentColor" strokeWidth="2" />
|
| 32 |
+
<path d="M7 3v4M17 3v4M3 9h18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
| 33 |
+
<rect x="6" y="12" width="3" height="3" rx="0.8" fill="currentColor" />
|
| 34 |
+
<rect x="11" y="12" width="3" height="3" rx="0.8" fill="currentColor" opacity="0.72" />
|
| 35 |
+
<rect x="16" y="12" width="3" height="3" rx="0.8" fill="currentColor" opacity="0.55" />
|
| 36 |
+
</svg>
|
| 37 |
+
),
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
label: 'Settings',
|
| 41 |
+
view: 'Settings',
|
| 42 |
+
icon: (
|
| 43 |
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
| 44 |
+
<path
|
| 45 |
+
d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.02-1.58a.5.5 0 0 0 .12-.63l-1.91-3.3a.5.5 0 0 0-.6-.22l-2.39.96a7.13 7.13 0 0 0-1.63-.94l-.36-2.53A.5.5 0 0 0 14.4 1h-3.82a.5.5 0 0 0-.49.42l-.36 2.53c-.58.23-1.13.54-1.63.94l-2.39-.96a.5.5 0 0 0-.6.22L3.2 7.45a.5.5 0 0 0 .12.63l2.02 1.58c-.04.31-.06.63-.06.94s.02.63.06.94L3.32 13.12a.5.5 0 0 0-.12.63l1.91 3.3a.5.5 0 0 0 .6.22l2.39-.96c.5.4 1.05.71 1.63.94l.36 2.53a.5.5 0 0 0 .49.42h3.82a.5.5 0 0 0 .49-.42l.36-2.53c.58-.23 1.13-.54 1.63-.94l2.39.96a.5.5 0 0 0 .6-.22l1.91-3.3a.5.5 0 0 0-.12-.63l-2.02-1.58ZM12 15.25A3.25 3.25 0 1 1 12 8.75a3.25 3.25 0 0 1 0 6.5Z"
|
| 46 |
+
fill="currentColor"
|
| 47 |
+
/>
|
| 48 |
+
</svg>
|
| 49 |
+
),
|
| 50 |
+
},
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
function Sidebar({ activeView, onSelectView }) {
|
| 54 |
+
return (
|
| 55 |
+
<aside className="sidebar" aria-label="Primary">
|
| 56 |
+
<div className="sidebar__brand">
|
| 57 |
+
<span className="sidebar__logo">HR</span>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<nav className="sidebar__nav">
|
| 61 |
+
{navigationItems.map((item) => (
|
| 62 |
+
<button
|
| 63 |
+
key={item.label}
|
| 64 |
+
type="button"
|
| 65 |
+
className={`sidebar__nav-item${item.view === activeView ? ' is-active' : ''}`}
|
| 66 |
+
aria-current={item.view === activeView ? 'page' : undefined}
|
| 67 |
+
aria-label={item.label}
|
| 68 |
+
data-label={item.label}
|
| 69 |
+
onClick={() => onSelectView(item.view)}
|
| 70 |
+
>
|
| 71 |
+
<span className="sidebar__nav-icon" aria-hidden="true">
|
| 72 |
+
{item.icon}
|
| 73 |
+
</span>
|
| 74 |
+
<span className="sidebar__nav-label">{item.label}</span>
|
| 75 |
+
</button>
|
| 76 |
+
))}
|
| 77 |
+
</nav>
|
| 78 |
+
</aside>
|
| 79 |
+
)
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
export default Sidebar
|
HR System/src/components/StageColumn.jsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import CandidateCard from './CandidateCard'
|
| 2 |
+
import EmptyState from './EmptyState'
|
| 3 |
+
|
| 4 |
+
function StageColumn({ stage, candidates, onSelectCandidate }) {
|
| 5 |
+
return (
|
| 6 |
+
<section className="stage-column">
|
| 7 |
+
<header className="stage-column__header">
|
| 8 |
+
<div>
|
| 9 |
+
<span className="stage-column__title">{stage.name}</span>
|
| 10 |
+
<span className="stage-column__count">{candidates.length}</span>
|
| 11 |
+
</div>
|
| 12 |
+
<span className="stage-column__detail">Detail</span>
|
| 13 |
+
</header>
|
| 14 |
+
|
| 15 |
+
<div className="stage-column__content">
|
| 16 |
+
{candidates.length ? (
|
| 17 |
+
candidates.map((candidate) => (
|
| 18 |
+
<CandidateCard
|
| 19 |
+
key={candidate.id}
|
| 20 |
+
candidate={candidate}
|
| 21 |
+
onClick={() => onSelectCandidate(candidate)}
|
| 22 |
+
/>
|
| 23 |
+
))
|
| 24 |
+
) : (
|
| 25 |
+
<EmptyState
|
| 26 |
+
title={`No candidates in ${stage.name}`}
|
| 27 |
+
description="Try adjusting the search or filter options to reveal more profiles."
|
| 28 |
+
/>
|
| 29 |
+
)}
|
| 30 |
+
</div>
|
| 31 |
+
</section>
|
| 32 |
+
)
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
export default StageColumn
|
HR System/src/components/TabsNav.jsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function TabsNav({ tabs, activeTab, onSelectTab }) {
|
| 2 |
+
return (
|
| 3 |
+
<nav className="tabs-nav" aria-label="Candidate sections">
|
| 4 |
+
{tabs.map((tab) => (
|
| 5 |
+
<button
|
| 6 |
+
key={tab}
|
| 7 |
+
type="button"
|
| 8 |
+
className={`tabs-nav__item${tab === activeTab ? ' is-active' : ''}`}
|
| 9 |
+
onClick={() => onSelectTab(tab)}
|
| 10 |
+
>
|
| 11 |
+
{tab}
|
| 12 |
+
</button>
|
| 13 |
+
))}
|
| 14 |
+
</nav>
|
| 15 |
+
)
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
export default TabsNav
|
HR System/src/components/TopActions.jsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function TopActions({ selectedCount, onCreateCandidate }) {
|
| 2 |
+
return (
|
| 3 |
+
<div className="top-actions">
|
| 4 |
+
<div className="top-actions__summary">
|
| 5 |
+
<span className="top-actions__label">Pipeline snapshot</span>
|
| 6 |
+
<strong>{selectedCount} visible candidates</strong>
|
| 7 |
+
</div>
|
| 8 |
+
<button type="button" className="primary-button" onClick={onCreateCandidate}>
|
| 9 |
+
Add candidate
|
| 10 |
+
</button>
|
| 11 |
+
</div>
|
| 12 |
+
)
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export default TopActions
|
HR System/src/data/dummyCandidates.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const stageOrder = ['Applied', 'Screening', 'Interview', 'Test']
|
| 2 |
+
|
| 3 |
+
export const dashboardTabs = [
|
| 4 |
+
'Candidates',
|
| 5 |
+
'Job Info',
|
| 6 |
+
'Calendar',
|
| 7 |
+
'Score Card',
|
| 8 |
+
'Activity',
|
| 9 |
+
'Application Form',
|
| 10 |
+
'Automation',
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
export const dummyCandidates = [
|
| 14 |
+
{
|
| 15 |
+
id: 'cand-001',
|
| 16 |
+
name: 'Marlon Reynolds',
|
| 17 |
+
role: 'Research and Development Officer',
|
| 18 |
+
appliedPosition: 'Research and Development Officer',
|
| 19 |
+
stage: 'Applied',
|
| 20 |
+
applicationDate: '2023-10-29',
|
| 21 |
+
overallScore: 3.5,
|
| 22 |
+
referred: true,
|
| 23 |
+
assessmentAdded: false,
|
| 24 |
+
email: 'marlon.reynolds@example.com',
|
| 25 |
+
phone: '+94 77 210 1188',
|
| 26 |
+
location: 'Colombo, Sri Lanka',
|
| 27 |
+
notes: 'Strong research exposure and a clear interest in process improvement.',
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
id: 'cand-002',
|
| 31 |
+
name: 'Regina Hane',
|
| 32 |
+
role: 'Research and Development Officer',
|
| 33 |
+
appliedPosition: 'Research and Development Officer',
|
| 34 |
+
stage: 'Applied',
|
| 35 |
+
applicationDate: '2023-10-29',
|
| 36 |
+
overallScore: 2,
|
| 37 |
+
referred: false,
|
| 38 |
+
assessmentAdded: false,
|
| 39 |
+
email: 'regina.hane@example.com',
|
| 40 |
+
phone: '+94 71 110 4412',
|
| 41 |
+
location: 'Gampaha, Sri Lanka',
|
| 42 |
+
notes: 'Needs stronger case study examples, but the fundamentals are solid.',
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
id: 'cand-003',
|
| 46 |
+
name: 'Curtis Baumbach',
|
| 47 |
+
role: 'Research and Development Officer',
|
| 48 |
+
appliedPosition: 'Research and Development Officer',
|
| 49 |
+
stage: 'Applied',
|
| 50 |
+
applicationDate: '2023-10-29',
|
| 51 |
+
overallScore: 3,
|
| 52 |
+
referred: true,
|
| 53 |
+
assessmentAdded: false,
|
| 54 |
+
email: 'curtis.baumbach@example.com',
|
| 55 |
+
phone: '+94 70 314 6088',
|
| 56 |
+
location: 'Kandy, Sri Lanka',
|
| 57 |
+
notes: 'Good communication and reliable background in analytical work.',
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
id: 'cand-004',
|
| 61 |
+
name: 'Jaime Anderson',
|
| 62 |
+
role: 'Research and Development Officer',
|
| 63 |
+
appliedPosition: 'Research and Development Officer',
|
| 64 |
+
stage: 'Applied',
|
| 65 |
+
applicationDate: '2023-10-29',
|
| 66 |
+
overallScore: 0,
|
| 67 |
+
referred: false,
|
| 68 |
+
assessmentAdded: true,
|
| 69 |
+
email: 'jaime.anderson@example.com',
|
| 70 |
+
phone: '+94 76 623 3311',
|
| 71 |
+
location: 'Negombo, Sri Lanka',
|
| 72 |
+
notes: 'Assessment pending review by the hiring manager.',
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
id: 'cand-005',
|
| 76 |
+
name: 'Kristi Sipes',
|
| 77 |
+
role: 'Research and Development Officer',
|
| 78 |
+
appliedPosition: 'Research and Development Officer',
|
| 79 |
+
stage: 'Screening',
|
| 80 |
+
applicationDate: '2023-10-20',
|
| 81 |
+
overallScore: 3.5,
|
| 82 |
+
referred: false,
|
| 83 |
+
assessmentAdded: true,
|
| 84 |
+
email: 'kristi.sipes@example.com',
|
| 85 |
+
phone: '+94 72 702 3391',
|
| 86 |
+
location: 'Colombo, Sri Lanka',
|
| 87 |
+
notes: 'Screened successfully. Ready for a deeper interview discussion.',
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
id: 'cand-006',
|
| 91 |
+
name: 'Randy Dibbert',
|
| 92 |
+
role: 'Research and Development Officer',
|
| 93 |
+
appliedPosition: 'Research and Development Officer',
|
| 94 |
+
stage: 'Screening',
|
| 95 |
+
applicationDate: '2023-10-18',
|
| 96 |
+
overallScore: 3.5,
|
| 97 |
+
referred: false,
|
| 98 |
+
assessmentAdded: true,
|
| 99 |
+
email: 'randy.dibbert@example.com',
|
| 100 |
+
phone: '+94 77 324 9084',
|
| 101 |
+
location: 'Matara, Sri Lanka',
|
| 102 |
+
notes: 'Balanced profile with good technical clarity and calm communication.',
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
id: 'cand-007',
|
| 106 |
+
name: 'Jane Anderson',
|
| 107 |
+
role: 'Research and Development Officer',
|
| 108 |
+
appliedPosition: 'Research and Development Officer',
|
| 109 |
+
stage: 'Screening',
|
| 110 |
+
applicationDate: '2023-10-18',
|
| 111 |
+
overallScore: 0,
|
| 112 |
+
referred: false,
|
| 113 |
+
assessmentAdded: false,
|
| 114 |
+
email: 'jane.anderson@example.com',
|
| 115 |
+
phone: '+94 77 888 9010',
|
| 116 |
+
location: 'Kotte, Sri Lanka',
|
| 117 |
+
notes: 'Waiting for final screening notes before advancing.',
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
id: 'cand-008',
|
| 121 |
+
name: 'Shelia Doyle',
|
| 122 |
+
role: 'Research and Development Officer',
|
| 123 |
+
appliedPosition: 'Research and Development Officer',
|
| 124 |
+
stage: 'Screening',
|
| 125 |
+
applicationDate: '2023-10-13',
|
| 126 |
+
overallScore: 4.5,
|
| 127 |
+
referred: true,
|
| 128 |
+
assessmentAdded: true,
|
| 129 |
+
email: 'shelia.doyle@example.com',
|
| 130 |
+
phone: '+94 75 901 2234',
|
| 131 |
+
location: 'Galle, Sri Lanka',
|
| 132 |
+
notes: 'Strong review. Good candidate to move into interview.',
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
id: 'cand-009',
|
| 136 |
+
name: 'Cassandra Hartmann',
|
| 137 |
+
role: 'Research and Development Officer',
|
| 138 |
+
appliedPosition: 'Research and Development Officer',
|
| 139 |
+
stage: 'Screening',
|
| 140 |
+
applicationDate: '2023-10-11',
|
| 141 |
+
overallScore: 4,
|
| 142 |
+
referred: false,
|
| 143 |
+
assessmentAdded: true,
|
| 144 |
+
email: 'cassandra.hartmann@example.com',
|
| 145 |
+
phone: '+94 71 593 8844',
|
| 146 |
+
location: 'Kurunegala, Sri Lanka',
|
| 147 |
+
notes: 'Very organized candidate with clear domain thinking.',
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
id: 'cand-010',
|
| 151 |
+
name: 'Cameron Dickens',
|
| 152 |
+
role: 'Research and Development Officer',
|
| 153 |
+
appliedPosition: 'Research and Development Officer',
|
| 154 |
+
stage: 'Interview',
|
| 155 |
+
applicationDate: '2023-09-03',
|
| 156 |
+
overallScore: 4,
|
| 157 |
+
referred: false,
|
| 158 |
+
assessmentAdded: true,
|
| 159 |
+
email: 'cameron.dickens@example.com',
|
| 160 |
+
phone: '+94 77 112 4455',
|
| 161 |
+
location: 'Colombo, Sri Lanka',
|
| 162 |
+
notes: 'Interview questions were answered clearly with strong ownership.',
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
id: 'cand-011',
|
| 166 |
+
name: 'Merle Vandervort',
|
| 167 |
+
role: 'Research and Development Officer',
|
| 168 |
+
appliedPosition: 'Research and Development Officer',
|
| 169 |
+
stage: 'Interview',
|
| 170 |
+
applicationDate: '2023-09-09',
|
| 171 |
+
overallScore: 4,
|
| 172 |
+
referred: false,
|
| 173 |
+
assessmentAdded: true,
|
| 174 |
+
email: 'merle.vandervort@example.com',
|
| 175 |
+
phone: '+94 76 501 6721',
|
| 176 |
+
location: 'Nugegoda, Sri Lanka',
|
| 177 |
+
notes: 'Technically strong and comfortable with structured problem solving.',
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
id: 'cand-012',
|
| 181 |
+
name: 'Jasmine Wiza',
|
| 182 |
+
role: 'Research and Development Officer',
|
| 183 |
+
appliedPosition: 'Research and Development Officer',
|
| 184 |
+
stage: 'Interview',
|
| 185 |
+
applicationDate: '2023-09-10',
|
| 186 |
+
overallScore: 0,
|
| 187 |
+
referred: false,
|
| 188 |
+
assessmentAdded: false,
|
| 189 |
+
email: 'jasmine.wiza@example.com',
|
| 190 |
+
phone: '+94 75 302 2199',
|
| 191 |
+
location: 'Moratuwa, Sri Lanka',
|
| 192 |
+
notes: 'Interview feedback pending from the panel.',
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
id: 'cand-013',
|
| 196 |
+
name: 'Lola Kirlin',
|
| 197 |
+
role: 'Research and Development Officer',
|
| 198 |
+
appliedPosition: 'Research and Development Officer',
|
| 199 |
+
stage: 'Test',
|
| 200 |
+
applicationDate: '2023-09-03',
|
| 201 |
+
overallScore: 4.5,
|
| 202 |
+
referred: false,
|
| 203 |
+
assessmentAdded: true,
|
| 204 |
+
email: 'lola.kirlin@example.com',
|
| 205 |
+
phone: '+94 77 201 9080',
|
| 206 |
+
location: 'Battaramulla, Sri Lanka',
|
| 207 |
+
notes: 'Excellent test result. Candidate looks ready for a final decision.',
|
| 208 |
+
},
|
| 209 |
+
{
|
| 210 |
+
id: 'cand-014',
|
| 211 |
+
name: 'Virgil Larkin',
|
| 212 |
+
role: 'Research and Development Officer',
|
| 213 |
+
appliedPosition: 'Research and Development Officer',
|
| 214 |
+
stage: 'Test',
|
| 215 |
+
applicationDate: '2023-09-03',
|
| 216 |
+
overallScore: 0,
|
| 217 |
+
referred: false,
|
| 218 |
+
assessmentAdded: false,
|
| 219 |
+
email: 'virgil.larkin@example.com',
|
| 220 |
+
phone: '+94 72 801 7744',
|
| 221 |
+
location: 'Dehiwala, Sri Lanka',
|
| 222 |
+
notes: 'Test assignment has been scheduled but not yet submitted.',
|
| 223 |
+
},
|
| 224 |
+
]
|
HR System/src/index.css
CHANGED
|
@@ -1,111 +1,3 @@
|
|
| 1 |
:root {
|
| 2 |
-
-
|
| 3 |
-
--text-h: #08060d;
|
| 4 |
-
--bg: #fff;
|
| 5 |
-
--border: #e5e4e7;
|
| 6 |
-
--code-bg: #f4f3ec;
|
| 7 |
-
--accent: #aa3bff;
|
| 8 |
-
--accent-bg: rgba(170, 59, 255, 0.1);
|
| 9 |
-
--accent-border: rgba(170, 59, 255, 0.5);
|
| 10 |
-
--social-bg: rgba(244, 243, 236, 0.5);
|
| 11 |
-
--shadow:
|
| 12 |
-
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
| 13 |
-
|
| 14 |
-
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
| 15 |
-
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
| 16 |
-
--mono: ui-monospace, Consolas, monospace;
|
| 17 |
-
|
| 18 |
-
font: 18px/145% var(--sans);
|
| 19 |
-
letter-spacing: 0.18px;
|
| 20 |
-
color-scheme: light dark;
|
| 21 |
-
color: var(--text);
|
| 22 |
-
background: var(--bg);
|
| 23 |
-
font-synthesis: none;
|
| 24 |
-
text-rendering: optimizeLegibility;
|
| 25 |
-
-webkit-font-smoothing: antialiased;
|
| 26 |
-
-moz-osx-font-smoothing: grayscale;
|
| 27 |
-
|
| 28 |
-
@media (max-width: 1024px) {
|
| 29 |
-
font-size: 16px;
|
| 30 |
-
}
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
@media (prefers-color-scheme: dark) {
|
| 34 |
-
:root {
|
| 35 |
-
--text: #9ca3af;
|
| 36 |
-
--text-h: #f3f4f6;
|
| 37 |
-
--bg: #16171d;
|
| 38 |
-
--border: #2e303a;
|
| 39 |
-
--code-bg: #1f2028;
|
| 40 |
-
--accent: #c084fc;
|
| 41 |
-
--accent-bg: rgba(192, 132, 252, 0.15);
|
| 42 |
-
--accent-border: rgba(192, 132, 252, 0.5);
|
| 43 |
-
--social-bg: rgba(47, 48, 58, 0.5);
|
| 44 |
-
--shadow:
|
| 45 |
-
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
#social .button-icon {
|
| 49 |
-
filter: invert(1) brightness(2);
|
| 50 |
-
}
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
body {
|
| 54 |
-
margin: 0;
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
#root {
|
| 58 |
-
width: 1126px;
|
| 59 |
-
max-width: 100%;
|
| 60 |
-
margin: 0 auto;
|
| 61 |
-
text-align: center;
|
| 62 |
-
border-inline: 1px solid var(--border);
|
| 63 |
-
min-height: 100svh;
|
| 64 |
-
display: flex;
|
| 65 |
-
flex-direction: column;
|
| 66 |
-
box-sizing: border-box;
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
h1,
|
| 70 |
-
h2 {
|
| 71 |
-
font-family: var(--heading);
|
| 72 |
-
font-weight: 500;
|
| 73 |
-
color: var(--text-h);
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
h1 {
|
| 77 |
-
font-size: 56px;
|
| 78 |
-
letter-spacing: -1.68px;
|
| 79 |
-
margin: 32px 0;
|
| 80 |
-
@media (max-width: 1024px) {
|
| 81 |
-
font-size: 36px;
|
| 82 |
-
margin: 20px 0;
|
| 83 |
-
}
|
| 84 |
-
}
|
| 85 |
-
h2 {
|
| 86 |
-
font-size: 24px;
|
| 87 |
-
line-height: 118%;
|
| 88 |
-
letter-spacing: -0.24px;
|
| 89 |
-
margin: 0 0 8px;
|
| 90 |
-
@media (max-width: 1024px) {
|
| 91 |
-
font-size: 20px;
|
| 92 |
-
}
|
| 93 |
-
}
|
| 94 |
-
p {
|
| 95 |
-
margin: 0;
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
code,
|
| 99 |
-
.counter {
|
| 100 |
-
font-family: var(--mono);
|
| 101 |
-
display: inline-flex;
|
| 102 |
-
border-radius: 4px;
|
| 103 |
-
color: var(--text-h);
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
code {
|
| 107 |
-
font-size: 15px;
|
| 108 |
-
line-height: 135%;
|
| 109 |
-
padding: 4px 8px;
|
| 110 |
-
background: var(--code-bg);
|
| 111 |
}
|
|
|
|
| 1 |
:root {
|
| 2 |
+
color-scheme: light;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
}
|
HR System/src/main.jsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { StrictMode } from 'react'
|
| 2 |
import { createRoot } from 'react-dom/client'
|
| 3 |
import './index.css'
|
|
|
|
| 4 |
import App from './App.jsx'
|
| 5 |
|
| 6 |
createRoot(document.getElementById('root')).render(
|
|
|
|
| 1 |
import { StrictMode } from 'react'
|
| 2 |
import { createRoot } from 'react-dom/client'
|
| 3 |
import './index.css'
|
| 4 |
+
import './styles/global.css'
|
| 5 |
import App from './App.jsx'
|
| 6 |
|
| 7 |
createRoot(document.getElementById('root')).render(
|
HR System/src/services/candidateApi.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { dummyCandidates } from '../data/dummyCandidates'
|
| 2 |
+
|
| 3 |
+
const clone = (value) => JSON.parse(JSON.stringify(value))
|
| 4 |
+
|
| 5 |
+
const wait = (time = 180) =>
|
| 6 |
+
new Promise((resolve) => {
|
| 7 |
+
window.setTimeout(resolve, time)
|
| 8 |
+
})
|
| 9 |
+
|
| 10 |
+
export async function getCandidates() {
|
| 11 |
+
await wait()
|
| 12 |
+
return clone(dummyCandidates)
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export async function getCandidateById(id) {
|
| 16 |
+
await wait(120)
|
| 17 |
+
return clone(dummyCandidates.find((candidate) => candidate.id === id) || null)
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export async function createCandidate(candidate) {
|
| 21 |
+
await wait(120)
|
| 22 |
+
return {
|
| 23 |
+
...clone(candidate),
|
| 24 |
+
id: crypto.randomUUID(),
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
export async function updateCandidate(id, updates) {
|
| 29 |
+
await wait(120)
|
| 30 |
+
return {
|
| 31 |
+
id,
|
| 32 |
+
...clone(updates),
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
export async function deleteCandidate(id) {
|
| 37 |
+
await wait(120)
|
| 38 |
+
return { id, deleted: true }
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
export async function updateCandidateStage(id, stage) {
|
| 42 |
+
await wait(120)
|
| 43 |
+
return { id, stage }
|
| 44 |
+
}
|
HR System/src/styles/global.css
ADDED
|
@@ -0,0 +1,1229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg: #f1f5f9;
|
| 3 |
+
--bg-soft: rgba(255, 255, 255, 0.7);
|
| 4 |
+
--panel: #ffffff;
|
| 5 |
+
--panel-strong: #ffffff;
|
| 6 |
+
--panel-muted: #f8fafc;
|
| 7 |
+
--text: #334155;
|
| 8 |
+
--text-strong: #0f172a;
|
| 9 |
+
--border: rgba(148, 163, 184, 0.24);
|
| 10 |
+
--shadow: 0 18px 40px rgba(15, 23, 42, 0.1);
|
| 11 |
+
--shadow-soft: 0 10px 24px rgba(15, 23, 42, 0.08);
|
| 12 |
+
--accent: #1f8a70;
|
| 13 |
+
--accent-strong: #16624f;
|
| 14 |
+
--accent-soft: rgba(31, 138, 112, 0.12);
|
| 15 |
+
--accent-alt: #2956f6;
|
| 16 |
+
--success: #2f9e5c;
|
| 17 |
+
--warning: #e49b11;
|
| 18 |
+
--danger: #dc4c64;
|
| 19 |
+
--radius-xl: 28px;
|
| 20 |
+
--radius-lg: 20px;
|
| 21 |
+
--radius-md: 14px;
|
| 22 |
+
--radius-sm: 10px;
|
| 23 |
+
--font-body: 'Segoe UI', Inter, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
| 24 |
+
--font-head: 'Segoe UI', Inter, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
html[data-theme='dark'] {
|
| 28 |
+
--bg: #0f131b;
|
| 29 |
+
--bg-soft: rgba(15, 19, 27, 0.74);
|
| 30 |
+
--panel: #171d28;
|
| 31 |
+
--panel-strong: #1d2432;
|
| 32 |
+
--panel-muted: #111723;
|
| 33 |
+
--text: #aeb8ca;
|
| 34 |
+
--text-strong: #f4f7fb;
|
| 35 |
+
--border: rgba(148, 163, 184, 0.16);
|
| 36 |
+
--shadow: 0 22px 44px rgba(0, 0, 0, 0.38);
|
| 37 |
+
--shadow-soft: 0 14px 28px rgba(0, 0, 0, 0.28);
|
| 38 |
+
--accent: #57c7a6;
|
| 39 |
+
--accent-strong: #2aa584;
|
| 40 |
+
--accent-soft: rgba(87, 199, 166, 0.14);
|
| 41 |
+
--accent-alt: #7b93ff;
|
| 42 |
+
--success: #57c7a6;
|
| 43 |
+
--warning: #f1b84b;
|
| 44 |
+
--danger: #ff788f;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
* {
|
| 48 |
+
box-sizing: border-box;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
html {
|
| 52 |
+
min-height: 100%;
|
| 53 |
+
background:
|
| 54 |
+
radial-gradient(circle at top left, rgba(31, 138, 112, 0.1), transparent 28%),
|
| 55 |
+
radial-gradient(circle at top right, rgba(41, 86, 246, 0.09), transparent 24%),
|
| 56 |
+
linear-gradient(180deg, var(--bg) 0%, var(--bg) 100%);
|
| 57 |
+
color: var(--text);
|
| 58 |
+
font-family: var(--font-body);
|
| 59 |
+
font-size: 16px;
|
| 60 |
+
line-height: 1.5;
|
| 61 |
+
font-synthesis: none;
|
| 62 |
+
text-rendering: optimizeLegibility;
|
| 63 |
+
-webkit-font-smoothing: antialiased;
|
| 64 |
+
-moz-osx-font-smoothing: grayscale;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
body {
|
| 68 |
+
margin: 0;
|
| 69 |
+
min-height: 100vh;
|
| 70 |
+
background: transparent;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
button,
|
| 74 |
+
input,
|
| 75 |
+
select {
|
| 76 |
+
font: inherit;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
button {
|
| 80 |
+
cursor: pointer;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
img {
|
| 84 |
+
max-width: 100%;
|
| 85 |
+
display: block;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
#root {
|
| 89 |
+
min-height: 100vh;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.sr-only {
|
| 93 |
+
position: absolute;
|
| 94 |
+
width: 1px;
|
| 95 |
+
height: 1px;
|
| 96 |
+
padding: 0;
|
| 97 |
+
margin: -1px;
|
| 98 |
+
overflow: hidden;
|
| 99 |
+
clip: rect(0, 0, 0, 0);
|
| 100 |
+
white-space: nowrap;
|
| 101 |
+
border: 0;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.app-shell {
|
| 105 |
+
min-height: 100vh;
|
| 106 |
+
display: grid;
|
| 107 |
+
grid-template-columns: 92px minmax(0, 1fr);
|
| 108 |
+
gap: 18px;
|
| 109 |
+
padding: 14px;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.sidebar {
|
| 113 |
+
display: flex;
|
| 114 |
+
flex-direction: column;
|
| 115 |
+
align-items: center;
|
| 116 |
+
justify-content: space-between;
|
| 117 |
+
gap: 22px;
|
| 118 |
+
padding: 18px 10px;
|
| 119 |
+
border-radius: 28px;
|
| 120 |
+
background: var(--panel);
|
| 121 |
+
border: 1px solid var(--border);
|
| 122 |
+
box-shadow: var(--shadow-soft);
|
| 123 |
+
position: sticky;
|
| 124 |
+
top: 14px;
|
| 125 |
+
height: calc(100vh - 28px);
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.sidebar__brand {
|
| 129 |
+
width: 100%;
|
| 130 |
+
display: flex;
|
| 131 |
+
justify-content: center;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.sidebar__logo {
|
| 135 |
+
width: 56px;
|
| 136 |
+
height: 56px;
|
| 137 |
+
border-radius: 18px;
|
| 138 |
+
display: grid;
|
| 139 |
+
place-items: center;
|
| 140 |
+
background: linear-gradient(135deg, rgba(31, 138, 112, 0.16), rgba(41, 86, 246, 0.18));
|
| 141 |
+
color: var(--text-strong);
|
| 142 |
+
font-weight: 800;
|
| 143 |
+
letter-spacing: 0.08em;
|
| 144 |
+
text-transform: lowercase;
|
| 145 |
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.sidebar__nav,
|
| 149 |
+
.sidebar__footer {
|
| 150 |
+
width: 100%;
|
| 151 |
+
display: grid;
|
| 152 |
+
gap: 10px;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.sidebar__nav-item {
|
| 156 |
+
width: 100%;
|
| 157 |
+
min-height: 54px;
|
| 158 |
+
padding: 0;
|
| 159 |
+
border: 0;
|
| 160 |
+
border-radius: 18px;
|
| 161 |
+
background: transparent;
|
| 162 |
+
color: var(--text);
|
| 163 |
+
display: grid;
|
| 164 |
+
justify-items: center;
|
| 165 |
+
align-content: center;
|
| 166 |
+
gap: 6px;
|
| 167 |
+
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.sidebar__nav-item:hover {
|
| 171 |
+
background: var(--accent-soft);
|
| 172 |
+
color: var(--text-strong);
|
| 173 |
+
transform: translateY(-1px);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.sidebar__nav-item.is-active {
|
| 177 |
+
background: linear-gradient(135deg, rgba(31, 138, 112, 0.18), rgba(41, 86, 246, 0.16));
|
| 178 |
+
color: var(--text-strong);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.sidebar__nav-item--ghost {
|
| 182 |
+
background: var(--panel-muted);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.sidebar__nav-icon {
|
| 186 |
+
width: 18px;
|
| 187 |
+
height: 18px;
|
| 188 |
+
border-radius: 6px;
|
| 189 |
+
background: currentColor;
|
| 190 |
+
opacity: 0.7;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.sidebar__nav-label {
|
| 194 |
+
font-size: 0.7rem;
|
| 195 |
+
font-weight: 700;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.workspace {
|
| 199 |
+
min-width: 0;
|
| 200 |
+
background: var(--bg-soft);
|
| 201 |
+
border: 1px solid var(--border);
|
| 202 |
+
border-radius: 32px;
|
| 203 |
+
padding: 18px;
|
| 204 |
+
box-shadow: var(--shadow);
|
| 205 |
+
backdrop-filter: blur(14px);
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.header {
|
| 209 |
+
display: flex;
|
| 210 |
+
align-items: flex-start;
|
| 211 |
+
justify-content: space-between;
|
| 212 |
+
gap: 18px;
|
| 213 |
+
padding: 10px 10px 18px;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.header__copy {
|
| 217 |
+
min-width: 0;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.header__eyebrow,
|
| 221 |
+
.top-actions__label,
|
| 222 |
+
.modal__eyebrow {
|
| 223 |
+
margin: 0 0 8px;
|
| 224 |
+
text-transform: uppercase;
|
| 225 |
+
letter-spacing: 0.16em;
|
| 226 |
+
font-size: 0.72rem;
|
| 227 |
+
font-weight: 700;
|
| 228 |
+
color: var(--accent-strong);
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
.header__title {
|
| 232 |
+
margin: 0;
|
| 233 |
+
color: var(--text-strong);
|
| 234 |
+
font-family: var(--font-head);
|
| 235 |
+
font-size: clamp(1.9rem, 3vw, 3rem);
|
| 236 |
+
line-height: 1.05;
|
| 237 |
+
letter-spacing: -0.04em;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.header__meta {
|
| 241 |
+
display: flex;
|
| 242 |
+
flex-wrap: wrap;
|
| 243 |
+
align-items: center;
|
| 244 |
+
gap: 10px;
|
| 245 |
+
margin-top: 12px;
|
| 246 |
+
color: var(--text);
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.header__dot {
|
| 250 |
+
width: 4px;
|
| 251 |
+
height: 4px;
|
| 252 |
+
border-radius: 50%;
|
| 253 |
+
background: currentColor;
|
| 254 |
+
opacity: 0.5;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.header__actions {
|
| 258 |
+
display: flex;
|
| 259 |
+
align-items: center;
|
| 260 |
+
gap: 12px;
|
| 261 |
+
flex-wrap: wrap;
|
| 262 |
+
justify-content: flex-end;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.header__crumb {
|
| 266 |
+
padding: 10px 14px;
|
| 267 |
+
border-radius: 18px;
|
| 268 |
+
background: var(--panel);
|
| 269 |
+
border: 1px solid var(--border);
|
| 270 |
+
display: grid;
|
| 271 |
+
gap: 2px;
|
| 272 |
+
box-shadow: var(--shadow-soft);
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
.header__crumb-label {
|
| 276 |
+
font-size: 0.72rem;
|
| 277 |
+
color: var(--text);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
.tabs-nav {
|
| 281 |
+
display: flex;
|
| 282 |
+
gap: 8px;
|
| 283 |
+
overflow-x: auto;
|
| 284 |
+
padding: 0 10px 10px;
|
| 285 |
+
scrollbar-width: none;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
.tabs-nav::-webkit-scrollbar,
|
| 289 |
+
.board-wrap::-webkit-scrollbar {
|
| 290 |
+
display: none;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
.tabs-nav__item {
|
| 294 |
+
border: 0;
|
| 295 |
+
background: transparent;
|
| 296 |
+
color: var(--text);
|
| 297 |
+
padding: 12px 16px;
|
| 298 |
+
border-radius: 999px;
|
| 299 |
+
font-weight: 700;
|
| 300 |
+
white-space: nowrap;
|
| 301 |
+
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.tabs-nav__item:hover,
|
| 305 |
+
.tabs-nav__item.is-active {
|
| 306 |
+
background: var(--panel);
|
| 307 |
+
color: var(--text-strong);
|
| 308 |
+
box-shadow: var(--shadow-soft);
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
.workspace__toolbar {
|
| 312 |
+
display: flex;
|
| 313 |
+
gap: 14px;
|
| 314 |
+
align-items: stretch;
|
| 315 |
+
justify-content: space-between;
|
| 316 |
+
flex-wrap: wrap;
|
| 317 |
+
padding: 10px;
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
.filter-bar {
|
| 321 |
+
flex: 1 1 640px;
|
| 322 |
+
display: grid;
|
| 323 |
+
grid-template-columns: minmax(220px, 2fr) repeat(2, minmax(160px, 1fr)) auto;
|
| 324 |
+
gap: 12px;
|
| 325 |
+
align-items: end;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
.field {
|
| 329 |
+
display: grid;
|
| 330 |
+
gap: 6px;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
.field--stacked {
|
| 334 |
+
gap: 10px;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
.field__label {
|
| 338 |
+
font-size: 0.75rem;
|
| 339 |
+
font-weight: 700;
|
| 340 |
+
color: var(--text);
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
.field input,
|
| 344 |
+
.field select {
|
| 345 |
+
width: 100%;
|
| 346 |
+
border: 1px solid var(--border);
|
| 347 |
+
border-radius: 16px;
|
| 348 |
+
background: var(--panel);
|
| 349 |
+
color: var(--text-strong);
|
| 350 |
+
padding: 14px 16px;
|
| 351 |
+
outline: none;
|
| 352 |
+
box-shadow: var(--shadow-soft);
|
| 353 |
+
transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
.field input:focus,
|
| 357 |
+
.field select:focus {
|
| 358 |
+
border-color: rgba(31, 138, 112, 0.48);
|
| 359 |
+
box-shadow: 0 0 0 4px rgba(31, 138, 112, 0.14);
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
.field--search input {
|
| 363 |
+
padding-left: 18px;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.top-actions {
|
| 367 |
+
min-width: 240px;
|
| 368 |
+
flex: 0 0 auto;
|
| 369 |
+
display: flex;
|
| 370 |
+
justify-content: space-between;
|
| 371 |
+
align-items: center;
|
| 372 |
+
gap: 14px;
|
| 373 |
+
padding: 14px 18px;
|
| 374 |
+
border-radius: 22px;
|
| 375 |
+
border: 1px solid var(--border);
|
| 376 |
+
background: var(--panel);
|
| 377 |
+
box-shadow: var(--shadow-soft);
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
.top-actions__summary {
|
| 381 |
+
display: grid;
|
| 382 |
+
gap: 4px;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
.chip,
|
| 386 |
+
.pill {
|
| 387 |
+
display: inline-flex;
|
| 388 |
+
align-items: center;
|
| 389 |
+
gap: 6px;
|
| 390 |
+
border-radius: 999px;
|
| 391 |
+
padding: 6px 10px;
|
| 392 |
+
font-size: 0.78rem;
|
| 393 |
+
font-weight: 700;
|
| 394 |
+
width: fit-content;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
.chip--success {
|
| 398 |
+
background: rgba(47, 158, 92, 0.14);
|
| 399 |
+
color: var(--success);
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.pill--score {
|
| 403 |
+
background: rgba(41, 86, 246, 0.1);
|
| 404 |
+
color: var(--accent-alt);
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
.pill--muted {
|
| 408 |
+
background: var(--panel-muted);
|
| 409 |
+
color: var(--text-strong);
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
.primary-button,
|
| 413 |
+
.ghost-button,
|
| 414 |
+
.icon-button {
|
| 415 |
+
border: 0;
|
| 416 |
+
border-radius: 16px;
|
| 417 |
+
font-weight: 700;
|
| 418 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
.primary-button {
|
| 422 |
+
padding: 14px 18px;
|
| 423 |
+
color: #ffffff;
|
| 424 |
+
background: linear-gradient(135deg, var(--accent), var(--accent-alt));
|
| 425 |
+
box-shadow: 0 14px 30px rgba(31, 138, 112, 0.28);
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
.primary-button:hover,
|
| 429 |
+
.ghost-button:hover,
|
| 430 |
+
.icon-button:hover {
|
| 431 |
+
transform: translateY(-1px);
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
.ghost-button,
|
| 435 |
+
.icon-button {
|
| 436 |
+
padding: 13px 16px;
|
| 437 |
+
background: var(--panel);
|
| 438 |
+
color: var(--text-strong);
|
| 439 |
+
box-shadow: var(--shadow-soft);
|
| 440 |
+
border: 1px solid var(--border);
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
.ghost-button--inline {
|
| 444 |
+
padding-inline: 14px;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
.banner {
|
| 448 |
+
margin: 0 10px 16px;
|
| 449 |
+
padding: 14px 16px;
|
| 450 |
+
border-radius: 18px;
|
| 451 |
+
background: var(--panel);
|
| 452 |
+
border: 1px solid var(--border);
|
| 453 |
+
box-shadow: var(--shadow-soft);
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
.banner--error {
|
| 457 |
+
border-color: rgba(220, 76, 100, 0.35);
|
| 458 |
+
color: var(--danger);
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
.board-wrap {
|
| 462 |
+
overflow-x: auto;
|
| 463 |
+
padding: 8px 6px 4px;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
.kanban-board {
|
| 467 |
+
display: grid;
|
| 468 |
+
grid-template-columns: repeat(4, minmax(240px, 1fr));
|
| 469 |
+
gap: 14px;
|
| 470 |
+
min-width: max-content;
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
.stage-column {
|
| 474 |
+
min-width: 0;
|
| 475 |
+
border-radius: 24px;
|
| 476 |
+
padding: 12px;
|
| 477 |
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0.18));
|
| 478 |
+
border: 1px solid var(--border);
|
| 479 |
+
box-shadow: var(--shadow-soft);
|
| 480 |
+
backdrop-filter: blur(12px);
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
html[data-theme='dark'] .stage-column {
|
| 484 |
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02));
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
.stage-column__header {
|
| 488 |
+
display: flex;
|
| 489 |
+
align-items: center;
|
| 490 |
+
justify-content: space-between;
|
| 491 |
+
gap: 10px;
|
| 492 |
+
padding: 4px 4px 12px;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
.stage-column__title {
|
| 496 |
+
display: inline-flex;
|
| 497 |
+
align-items: center;
|
| 498 |
+
gap: 8px;
|
| 499 |
+
font-weight: 800;
|
| 500 |
+
color: var(--text-strong);
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
.stage-column__title::before {
|
| 504 |
+
content: '';
|
| 505 |
+
width: 12px;
|
| 506 |
+
height: 12px;
|
| 507 |
+
border-radius: 999px;
|
| 508 |
+
background: linear-gradient(135deg, var(--accent), var(--accent-alt));
|
| 509 |
+
box-shadow: 0 0 0 4px rgba(31, 138, 112, 0.12);
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
.stage-column__count,
|
| 513 |
+
.stage-column__detail {
|
| 514 |
+
display: inline-flex;
|
| 515 |
+
align-items: center;
|
| 516 |
+
justify-content: center;
|
| 517 |
+
padding: 5px 10px;
|
| 518 |
+
border-radius: 999px;
|
| 519 |
+
background: var(--panel);
|
| 520 |
+
border: 1px solid var(--border);
|
| 521 |
+
color: var(--text-strong);
|
| 522 |
+
font-size: 0.78rem;
|
| 523 |
+
font-weight: 700;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.stage-column__content {
|
| 527 |
+
display: grid;
|
| 528 |
+
gap: 12px;
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
.candidate-card,
|
| 532 |
+
.empty-state {
|
| 533 |
+
width: 100%;
|
| 534 |
+
text-align: left;
|
| 535 |
+
border-radius: 20px;
|
| 536 |
+
border: 1px solid var(--border);
|
| 537 |
+
background: var(--panel-strong);
|
| 538 |
+
color: var(--text);
|
| 539 |
+
box-shadow: var(--shadow-soft);
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
.candidate-card {
|
| 543 |
+
padding: 14px;
|
| 544 |
+
display: grid;
|
| 545 |
+
gap: 12px;
|
| 546 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
.candidate-card:hover {
|
| 550 |
+
transform: translateY(-2px);
|
| 551 |
+
box-shadow: var(--shadow);
|
| 552 |
+
border-color: rgba(31, 138, 112, 0.26);
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
.candidate-card__top {
|
| 556 |
+
display: flex;
|
| 557 |
+
align-items: center;
|
| 558 |
+
gap: 12px;
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
.avatar {
|
| 562 |
+
width: 44px;
|
| 563 |
+
height: 44px;
|
| 564 |
+
border-radius: 16px;
|
| 565 |
+
display: grid;
|
| 566 |
+
place-items: center;
|
| 567 |
+
color: #ffffff;
|
| 568 |
+
font-weight: 800;
|
| 569 |
+
background: linear-gradient(135deg, var(--accent), var(--accent-alt));
|
| 570 |
+
flex: 0 0 auto;
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
.avatar--large {
|
| 574 |
+
width: 72px;
|
| 575 |
+
height: 72px;
|
| 576 |
+
border-radius: 24px;
|
| 577 |
+
font-size: 1.1rem;
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
.candidate-card__heading {
|
| 581 |
+
display: grid;
|
| 582 |
+
gap: 3px;
|
| 583 |
+
min-width: 0;
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
.candidate-card__heading strong,
|
| 587 |
+
.modal h2,
|
| 588 |
+
.modal__card strong,
|
| 589 |
+
.top-actions strong {
|
| 590 |
+
color: var(--text-strong);
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.candidate-card__heading span,
|
| 594 |
+
.candidate-card__action,
|
| 595 |
+
.modal__stat-label,
|
| 596 |
+
.modal__facts p,
|
| 597 |
+
.header__meta,
|
| 598 |
+
.top-actions__summary,
|
| 599 |
+
.empty-state p {
|
| 600 |
+
color: var(--text);
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
.candidate-card__body,
|
| 604 |
+
.candidate-card__footer,
|
| 605 |
+
.modal__grid {
|
| 606 |
+
display: flex;
|
| 607 |
+
align-items: center;
|
| 608 |
+
justify-content: space-between;
|
| 609 |
+
gap: 10px;
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
.candidate-card__footer {
|
| 613 |
+
padding-top: 8px;
|
| 614 |
+
border-top: 1px solid var(--border);
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
.candidate-card__action--accent {
|
| 618 |
+
color: var(--accent-strong);
|
| 619 |
+
font-weight: 700;
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
.candidate-card__chevron {
|
| 623 |
+
color: var(--accent-strong);
|
| 624 |
+
font-size: 1.4rem;
|
| 625 |
+
line-height: 1;
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
.empty-state {
|
| 629 |
+
padding: 20px;
|
| 630 |
+
display: grid;
|
| 631 |
+
gap: 8px;
|
| 632 |
+
background: var(--panel-muted);
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
.modal-backdrop {
|
| 636 |
+
position: fixed;
|
| 637 |
+
inset: 0;
|
| 638 |
+
background: rgba(15, 23, 42, 0.46);
|
| 639 |
+
backdrop-filter: blur(8px);
|
| 640 |
+
display: grid;
|
| 641 |
+
place-items: center;
|
| 642 |
+
padding: 20px;
|
| 643 |
+
z-index: 40;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
.modal {
|
| 647 |
+
width: min(960px, 100%);
|
| 648 |
+
max-height: min(88vh, 900px);
|
| 649 |
+
overflow: auto;
|
| 650 |
+
border-radius: 30px;
|
| 651 |
+
background: var(--panel);
|
| 652 |
+
border: 1px solid var(--border);
|
| 653 |
+
box-shadow: var(--shadow);
|
| 654 |
+
padding: 22px;
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
.modal__header {
|
| 658 |
+
display: flex;
|
| 659 |
+
align-items: flex-start;
|
| 660 |
+
justify-content: space-between;
|
| 661 |
+
gap: 16px;
|
| 662 |
+
margin-bottom: 18px;
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
.modal__header h2 {
|
| 666 |
+
margin: 0;
|
| 667 |
+
font-size: clamp(1.5rem, 2vw, 2rem);
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
.modal__content {
|
| 671 |
+
display: grid;
|
| 672 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
| 673 |
+
gap: 14px;
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
.modal__card {
|
| 677 |
+
border-radius: 22px;
|
| 678 |
+
padding: 18px;
|
| 679 |
+
background: var(--panel-muted);
|
| 680 |
+
border: 1px solid var(--border);
|
| 681 |
+
display: grid;
|
| 682 |
+
gap: 14px;
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
.modal__card--full {
|
| 686 |
+
grid-column: 1 / -1;
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
.modal__facts {
|
| 690 |
+
display: grid;
|
| 691 |
+
gap: 8px;
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
.modal__facts p,
|
| 695 |
+
.modal__card p {
|
| 696 |
+
margin: 0;
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
.modal__grid {
|
| 700 |
+
flex-wrap: wrap;
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
.modal__grid > div {
|
| 704 |
+
flex: 1 1 160px;
|
| 705 |
+
display: grid;
|
| 706 |
+
gap: 4px;
|
| 707 |
+
padding: 14px;
|
| 708 |
+
border-radius: 18px;
|
| 709 |
+
background: var(--panel);
|
| 710 |
+
border: 1px solid var(--border);
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
.modal__stat-label {
|
| 714 |
+
font-size: 0.78rem;
|
| 715 |
+
font-weight: 700;
|
| 716 |
+
text-transform: uppercase;
|
| 717 |
+
letter-spacing: 0.08em;
|
| 718 |
+
}
|
| 719 |
+
|
| 720 |
+
@media (max-width: 1180px) {
|
| 721 |
+
.app-shell {
|
| 722 |
+
grid-template-columns: 76px minmax(0, 1fr);
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
.filter-bar {
|
| 726 |
+
grid-template-columns: minmax(220px, 1fr) repeat(2, minmax(150px, 1fr));
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
.top-actions {
|
| 730 |
+
flex: 1 1 100%;
|
| 731 |
+
justify-content: space-between;
|
| 732 |
+
}
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
+
@media (max-width: 940px) {
|
| 736 |
+
.app-shell {
|
| 737 |
+
grid-template-columns: 1fr;
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
.sidebar {
|
| 741 |
+
position: static;
|
| 742 |
+
height: auto;
|
| 743 |
+
width: 100%;
|
| 744 |
+
flex-direction: row;
|
| 745 |
+
align-items: center;
|
| 746 |
+
justify-content: space-between;
|
| 747 |
+
overflow-x: auto;
|
| 748 |
+
}
|
| 749 |
+
|
| 750 |
+
.sidebar__nav,
|
| 751 |
+
.sidebar__footer {
|
| 752 |
+
width: auto;
|
| 753 |
+
display: flex;
|
| 754 |
+
gap: 8px;
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
.sidebar__nav-item {
|
| 758 |
+
min-width: 84px;
|
| 759 |
+
padding-inline: 12px;
|
| 760 |
+
}
|
| 761 |
+
|
| 762 |
+
.workspace {
|
| 763 |
+
border-radius: 28px;
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
.header,
|
| 767 |
+
.workspace__toolbar {
|
| 768 |
+
flex-direction: column;
|
| 769 |
+
}
|
| 770 |
+
|
| 771 |
+
.header__actions {
|
| 772 |
+
width: 100%;
|
| 773 |
+
justify-content: flex-start;
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
.filter-bar {
|
| 777 |
+
flex: 1 1 100%;
|
| 778 |
+
grid-template-columns: 1fr;
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
.modal__content {
|
| 782 |
+
grid-template-columns: 1fr;
|
| 783 |
+
}
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
+
@media (max-width: 640px) {
|
| 787 |
+
.app-shell {
|
| 788 |
+
padding: 10px;
|
| 789 |
+
gap: 10px;
|
| 790 |
+
}
|
| 791 |
+
|
| 792 |
+
.workspace {
|
| 793 |
+
padding: 12px;
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
.header {
|
| 797 |
+
padding-inline: 2px;
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
.tabs-nav,
|
| 801 |
+
.workspace__toolbar,
|
| 802 |
+
.kanban-board {
|
| 803 |
+
padding-inline: 0;
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
.kanban-board {
|
| 807 |
+
grid-template-columns: repeat(4, minmax(220px, 1fr));
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
.modal {
|
| 811 |
+
padding: 16px;
|
| 812 |
+
border-radius: 22px;
|
| 813 |
+
}
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
.sidebar {
|
| 817 |
+
width: 100%;
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
.sidebar__brand {
|
| 821 |
+
padding-top: 2px;
|
| 822 |
+
}
|
| 823 |
+
|
| 824 |
+
.sidebar__logo {
|
| 825 |
+
letter-spacing: 0.1em;
|
| 826 |
+
text-transform: uppercase;
|
| 827 |
+
}
|
| 828 |
+
|
| 829 |
+
.sidebar__nav-item {
|
| 830 |
+
position: relative;
|
| 831 |
+
padding: 0;
|
| 832 |
+
min-height: 58px;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
.sidebar__nav-icon {
|
| 836 |
+
width: 22px;
|
| 837 |
+
height: 22px;
|
| 838 |
+
display: grid;
|
| 839 |
+
place-items: center;
|
| 840 |
+
color: currentColor;
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
.sidebar__nav-icon svg {
|
| 844 |
+
width: 100%;
|
| 845 |
+
height: 100%;
|
| 846 |
+
display: block;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
.sidebar__nav-label {
|
| 850 |
+
display: none;
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
.sidebar__nav-item::after {
|
| 854 |
+
content: attr(data-label);
|
| 855 |
+
position: absolute;
|
| 856 |
+
left: calc(100% + 12px);
|
| 857 |
+
top: 50%;
|
| 858 |
+
transform: translateY(-50%) translateX(-4px);
|
| 859 |
+
opacity: 0;
|
| 860 |
+
pointer-events: none;
|
| 861 |
+
padding: 8px 10px;
|
| 862 |
+
border-radius: 12px;
|
| 863 |
+
background: var(--text-strong);
|
| 864 |
+
color: var(--panel);
|
| 865 |
+
font-size: 0.78rem;
|
| 866 |
+
font-weight: 700;
|
| 867 |
+
white-space: nowrap;
|
| 868 |
+
box-shadow: var(--shadow-soft);
|
| 869 |
+
transition: opacity 0.16s ease, transform 0.16s ease;
|
| 870 |
+
z-index: 2;
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
.sidebar__nav-item:hover::after,
|
| 874 |
+
.sidebar__nav-item:focus-visible::after {
|
| 875 |
+
opacity: 1;
|
| 876 |
+
transform: translateY(-50%) translateX(0);
|
| 877 |
+
}
|
| 878 |
+
|
| 879 |
+
.header__actions {
|
| 880 |
+
align-items: flex-start;
|
| 881 |
+
}
|
| 882 |
+
|
| 883 |
+
.header__crumb {
|
| 884 |
+
min-width: 160px;
|
| 885 |
+
}
|
| 886 |
+
|
| 887 |
+
.directory,
|
| 888 |
+
.calendar-view,
|
| 889 |
+
.settings-panel {
|
| 890 |
+
display: grid;
|
| 891 |
+
gap: 18px;
|
| 892 |
+
padding: 10px 10px 4px;
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
.directory__header,
|
| 896 |
+
.calendar-view__header,
|
| 897 |
+
.settings-panel__header {
|
| 898 |
+
display: flex;
|
| 899 |
+
align-items: flex-start;
|
| 900 |
+
justify-content: space-between;
|
| 901 |
+
gap: 16px;
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
.directory__eyebrow,
|
| 905 |
+
.calendar-view__eyebrow,
|
| 906 |
+
.settings-panel__eyebrow {
|
| 907 |
+
margin: 0 0 8px;
|
| 908 |
+
text-transform: uppercase;
|
| 909 |
+
letter-spacing: 0.16em;
|
| 910 |
+
font-size: 0.72rem;
|
| 911 |
+
font-weight: 700;
|
| 912 |
+
color: var(--accent-strong);
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
.directory h2,
|
| 916 |
+
.calendar-view h2,
|
| 917 |
+
.settings-panel h2 {
|
| 918 |
+
margin: 0;
|
| 919 |
+
color: var(--text-strong);
|
| 920 |
+
font-size: clamp(1.5rem, 2vw, 2.2rem);
|
| 921 |
+
letter-spacing: -0.03em;
|
| 922 |
+
}
|
| 923 |
+
|
| 924 |
+
.directory__header p,
|
| 925 |
+
.calendar-view__header p,
|
| 926 |
+
.settings-panel__header p,
|
| 927 |
+
.settings-panel__copy p {
|
| 928 |
+
margin: 8px 0 0;
|
| 929 |
+
color: var(--text);
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
.directory__count,
|
| 933 |
+
.calendar-view__badge {
|
| 934 |
+
min-width: 112px;
|
| 935 |
+
padding: 14px 16px;
|
| 936 |
+
border-radius: 20px;
|
| 937 |
+
background: var(--panel);
|
| 938 |
+
border: 1px solid var(--border);
|
| 939 |
+
box-shadow: var(--shadow-soft);
|
| 940 |
+
display: grid;
|
| 941 |
+
gap: 2px;
|
| 942 |
+
justify-items: center;
|
| 943 |
+
}
|
| 944 |
+
|
| 945 |
+
.directory__count strong,
|
| 946 |
+
.calendar-view__badge strong {
|
| 947 |
+
color: var(--text-strong);
|
| 948 |
+
font-size: 1.5rem;
|
| 949 |
+
line-height: 1;
|
| 950 |
+
}
|
| 951 |
+
|
| 952 |
+
.directory__count span,
|
| 953 |
+
.calendar-view__badge span {
|
| 954 |
+
color: var(--text);
|
| 955 |
+
font-size: 0.78rem;
|
| 956 |
+
font-weight: 700;
|
| 957 |
+
text-transform: uppercase;
|
| 958 |
+
letter-spacing: 0.08em;
|
| 959 |
+
}
|
| 960 |
+
|
| 961 |
+
.directory__filters {
|
| 962 |
+
display: grid;
|
| 963 |
+
grid-template-columns: minmax(240px, 2fr) repeat(2, minmax(160px, 1fr)) auto;
|
| 964 |
+
gap: 12px;
|
| 965 |
+
align-items: end;
|
| 966 |
+
}
|
| 967 |
+
|
| 968 |
+
.directory__list {
|
| 969 |
+
display: grid;
|
| 970 |
+
gap: 10px;
|
| 971 |
+
}
|
| 972 |
+
|
| 973 |
+
.directory__list-head,
|
| 974 |
+
.candidate-row {
|
| 975 |
+
display: grid;
|
| 976 |
+
grid-template-columns: minmax(220px, 1.4fr) minmax(220px, 1.1fr) minmax(180px, 1fr) minmax(120px, 0.5fr);
|
| 977 |
+
gap: 16px;
|
| 978 |
+
align-items: center;
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
.directory__list-head {
|
| 982 |
+
padding: 0 14px;
|
| 983 |
+
color: var(--text);
|
| 984 |
+
font-size: 0.76rem;
|
| 985 |
+
font-weight: 700;
|
| 986 |
+
text-transform: uppercase;
|
| 987 |
+
letter-spacing: 0.08em;
|
| 988 |
+
}
|
| 989 |
+
|
| 990 |
+
.candidate-row {
|
| 991 |
+
width: 100%;
|
| 992 |
+
border: 1px solid var(--border);
|
| 993 |
+
border-radius: 22px;
|
| 994 |
+
background: var(--panel);
|
| 995 |
+
box-shadow: var(--shadow-soft);
|
| 996 |
+
padding: 14px;
|
| 997 |
+
text-align: left;
|
| 998 |
+
color: var(--text);
|
| 999 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
.candidate-row:hover {
|
| 1003 |
+
transform: translateY(-2px);
|
| 1004 |
+
box-shadow: var(--shadow);
|
| 1005 |
+
border-color: rgba(31, 138, 112, 0.26);
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
.candidate-row__identity {
|
| 1009 |
+
display: flex;
|
| 1010 |
+
align-items: center;
|
| 1011 |
+
gap: 12px;
|
| 1012 |
+
min-width: 0;
|
| 1013 |
+
}
|
| 1014 |
+
|
| 1015 |
+
.candidate-row__nameblock {
|
| 1016 |
+
display: grid;
|
| 1017 |
+
gap: 3px;
|
| 1018 |
+
min-width: 0;
|
| 1019 |
+
}
|
| 1020 |
+
|
| 1021 |
+
.candidate-row__nameblock strong {
|
| 1022 |
+
color: var(--text-strong);
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
.candidate-row__nameblock span,
|
| 1026 |
+
.candidate-row__contact,
|
| 1027 |
+
.candidate-row__position {
|
| 1028 |
+
color: var(--text);
|
| 1029 |
+
font-size: 0.92rem;
|
| 1030 |
+
}
|
| 1031 |
+
|
| 1032 |
+
.candidate-row__contact,
|
| 1033 |
+
.candidate-row__position,
|
| 1034 |
+
.candidate-row__stage {
|
| 1035 |
+
display: grid;
|
| 1036 |
+
gap: 4px;
|
| 1037 |
+
}
|
| 1038 |
+
|
| 1039 |
+
.candidate-row__contact span {
|
| 1040 |
+
overflow: hidden;
|
| 1041 |
+
text-overflow: ellipsis;
|
| 1042 |
+
white-space: nowrap;
|
| 1043 |
+
}
|
| 1044 |
+
|
| 1045 |
+
.candidate-row__stage {
|
| 1046 |
+
justify-items: start;
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
.calendar-grid {
|
| 1050 |
+
display: grid;
|
| 1051 |
+
grid-template-columns: repeat(7, minmax(0, 1fr));
|
| 1052 |
+
gap: 10px;
|
| 1053 |
+
padding: 14px;
|
| 1054 |
+
border-radius: 28px;
|
| 1055 |
+
border: 1px solid var(--border);
|
| 1056 |
+
background: var(--panel-soft, var(--panel));
|
| 1057 |
+
box-shadow: var(--shadow-soft);
|
| 1058 |
+
}
|
| 1059 |
+
|
| 1060 |
+
.calendar-grid__weekday {
|
| 1061 |
+
padding: 10px 8px;
|
| 1062 |
+
text-align: center;
|
| 1063 |
+
color: var(--text);
|
| 1064 |
+
font-size: 0.76rem;
|
| 1065 |
+
font-weight: 700;
|
| 1066 |
+
text-transform: uppercase;
|
| 1067 |
+
letter-spacing: 0.08em;
|
| 1068 |
+
}
|
| 1069 |
+
|
| 1070 |
+
.calendar-grid__day {
|
| 1071 |
+
min-height: 88px;
|
| 1072 |
+
padding: 12px;
|
| 1073 |
+
border-radius: 20px;
|
| 1074 |
+
border: 1px solid var(--border);
|
| 1075 |
+
background: var(--panel);
|
| 1076 |
+
color: var(--text-strong);
|
| 1077 |
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02);
|
| 1078 |
+
display: flex;
|
| 1079 |
+
align-items: flex-start;
|
| 1080 |
+
justify-content: flex-start;
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
.calendar-grid__day.is-muted {
|
| 1084 |
+
color: rgba(148, 163, 184, 0.72);
|
| 1085 |
+
background: var(--panel-muted);
|
| 1086 |
+
}
|
| 1087 |
+
|
| 1088 |
+
.calendar-grid__day.is-today {
|
| 1089 |
+
border-color: rgba(31, 138, 112, 0.42);
|
| 1090 |
+
box-shadow: 0 0 0 4px rgba(31, 138, 112, 0.1);
|
| 1091 |
+
}
|
| 1092 |
+
|
| 1093 |
+
.settings-panel__card {
|
| 1094 |
+
display: flex;
|
| 1095 |
+
align-items: center;
|
| 1096 |
+
justify-content: space-between;
|
| 1097 |
+
gap: 20px;
|
| 1098 |
+
padding: 22px;
|
| 1099 |
+
border-radius: 28px;
|
| 1100 |
+
border: 1px solid var(--border);
|
| 1101 |
+
background: linear-gradient(135deg, rgba(31, 138, 112, 0.1), rgba(41, 86, 246, 0.08));
|
| 1102 |
+
box-shadow: var(--shadow-soft);
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
.settings-panel__copy {
|
| 1106 |
+
max-width: 640px;
|
| 1107 |
+
display: grid;
|
| 1108 |
+
gap: 8px;
|
| 1109 |
+
}
|
| 1110 |
+
|
| 1111 |
+
.settings-panel__label {
|
| 1112 |
+
display: inline-flex;
|
| 1113 |
+
align-items: center;
|
| 1114 |
+
width: fit-content;
|
| 1115 |
+
border-radius: 999px;
|
| 1116 |
+
padding: 6px 10px;
|
| 1117 |
+
background: var(--panel);
|
| 1118 |
+
color: var(--accent-strong);
|
| 1119 |
+
font-size: 0.76rem;
|
| 1120 |
+
font-weight: 700;
|
| 1121 |
+
letter-spacing: 0.04em;
|
| 1122 |
+
text-transform: uppercase;
|
| 1123 |
+
}
|
| 1124 |
+
|
| 1125 |
+
.theme-switch {
|
| 1126 |
+
min-width: 220px;
|
| 1127 |
+
border: 0;
|
| 1128 |
+
border-radius: 24px;
|
| 1129 |
+
padding: 16px;
|
| 1130 |
+
background: var(--panel);
|
| 1131 |
+
box-shadow: var(--shadow-soft);
|
| 1132 |
+
display: grid;
|
| 1133 |
+
gap: 14px;
|
| 1134 |
+
justify-items: center;
|
| 1135 |
+
}
|
| 1136 |
+
|
| 1137 |
+
.theme-switch__track {
|
| 1138 |
+
width: 100%;
|
| 1139 |
+
height: 56px;
|
| 1140 |
+
padding: 6px;
|
| 1141 |
+
border-radius: 999px;
|
| 1142 |
+
background: linear-gradient(135deg, rgba(31, 138, 112, 0.2), rgba(41, 86, 246, 0.18));
|
| 1143 |
+
position: relative;
|
| 1144 |
+
}
|
| 1145 |
+
|
| 1146 |
+
.theme-switch__thumb {
|
| 1147 |
+
position: absolute;
|
| 1148 |
+
top: 6px;
|
| 1149 |
+
left: 6px;
|
| 1150 |
+
width: 44px;
|
| 1151 |
+
height: 44px;
|
| 1152 |
+
border-radius: 50%;
|
| 1153 |
+
background: var(--panel);
|
| 1154 |
+
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.16);
|
| 1155 |
+
transition: transform 0.2s ease;
|
| 1156 |
+
}
|
| 1157 |
+
|
| 1158 |
+
.theme-switch.is-dark .theme-switch__thumb {
|
| 1159 |
+
transform: translateX(100%);
|
| 1160 |
+
}
|
| 1161 |
+
|
| 1162 |
+
.theme-switch__labels {
|
| 1163 |
+
width: 100%;
|
| 1164 |
+
display: flex;
|
| 1165 |
+
justify-content: space-between;
|
| 1166 |
+
color: var(--text-strong);
|
| 1167 |
+
font-weight: 700;
|
| 1168 |
+
}
|
| 1169 |
+
|
| 1170 |
+
.theme-switch__labels span:last-child {
|
| 1171 |
+
color: var(--text);
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
html[data-theme='dark'] .calendar-grid__day.is-muted {
|
| 1175 |
+
color: rgba(148, 163, 184, 0.58);
|
| 1176 |
+
}
|
| 1177 |
+
|
| 1178 |
+
@media (max-width: 1180px) {
|
| 1179 |
+
.directory__filters {
|
| 1180 |
+
grid-template-columns: minmax(220px, 1fr) repeat(2, minmax(150px, 1fr));
|
| 1181 |
+
}
|
| 1182 |
+
|
| 1183 |
+
.directory__list-head,
|
| 1184 |
+
.candidate-row {
|
| 1185 |
+
grid-template-columns: minmax(200px, 1.3fr) minmax(200px, 1fr) minmax(160px, 1fr) minmax(110px, 0.5fr);
|
| 1186 |
+
}
|
| 1187 |
+
}
|
| 1188 |
+
|
| 1189 |
+
@media (max-width: 940px) {
|
| 1190 |
+
.sidebar__nav-item::after {
|
| 1191 |
+
display: none;
|
| 1192 |
+
}
|
| 1193 |
+
|
| 1194 |
+
.directory__header,
|
| 1195 |
+
.calendar-view__header,
|
| 1196 |
+
.settings-panel__header,
|
| 1197 |
+
.settings-panel__card {
|
| 1198 |
+
flex-direction: column;
|
| 1199 |
+
}
|
| 1200 |
+
|
| 1201 |
+
.directory__filters,
|
| 1202 |
+
.directory__list-head,
|
| 1203 |
+
.candidate-row {
|
| 1204 |
+
grid-template-columns: 1fr;
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
.directory__list-head {
|
| 1208 |
+
display: none;
|
| 1209 |
+
}
|
| 1210 |
+
|
| 1211 |
+
.calendar-grid {
|
| 1212 |
+
gap: 8px;
|
| 1213 |
+
}
|
| 1214 |
+
|
| 1215 |
+
.calendar-grid__day {
|
| 1216 |
+
min-height: 72px;
|
| 1217 |
+
}
|
| 1218 |
+
}
|
| 1219 |
+
|
| 1220 |
+
@media (max-width: 640px) {
|
| 1221 |
+
.settings-panel__card {
|
| 1222 |
+
padding: 18px;
|
| 1223 |
+
}
|
| 1224 |
+
|
| 1225 |
+
.theme-switch {
|
| 1226 |
+
width: 100%;
|
| 1227 |
+
min-width: 0;
|
| 1228 |
+
}
|
| 1229 |
+
}
|