Skip to content

Commit 5cb3c30

Browse files
Merge pull request #3851 from OneCommunityGlobal/kristin-add-promotion-table-new-dev
Kristin - Implement Promotion Eligibility Table in Questionnaire Dashboard
2 parents 858d345 + 6dd270a commit 5cb3c30

3 files changed

Lines changed: 321 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { useEffect, useState } from 'react';
2+
import { useSelector } from 'react-redux';
3+
import styles from './PromotionTable.module.css';
4+
5+
const names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Edward', 'Fiona', 'Grace'];
6+
7+
function seededRandom(seed) {
8+
let x = Math.sin(seed) * 10000;
9+
return x - Math.floor(x);
10+
}
11+
12+
const dummyMembers = Array.from({ length: 45 }, (_, i) => ({
13+
id: i + 1,
14+
reviewer: names[i % names.length],
15+
hasMetWeekly: i % 2 === 0,
16+
requiredPRs: 5,
17+
totalReviews: Math.floor(seededRandom(i) * 10),
18+
remainingWeeks: Math.max(0, 4 - Math.floor(seededRandom(i + 1) * 4)),
19+
promote: i % 3 === 0,
20+
isNew: i < 15,
21+
}));
22+
23+
function MemberSection({ title, members, styles }) {
24+
return (
25+
<>
26+
<tr className={styles['sectionHeader']}>
27+
<td colSpan="7">{title}</td>
28+
</tr>
29+
{members.map(user => (
30+
<tr key={user.id}>
31+
<td />
32+
<td>{user.reviewer}</td>
33+
<td className={user.hasMetWeekly ? styles['statusMet'] : styles['statusNotMet']}>
34+
<span className={styles['statusIcon']}>{user.hasMetWeekly ? '✓' : '✗'}</span>
35+
{user.hasMetWeekly ? 'Has Met' : 'Has not Met'}
36+
</td>
37+
<td>{user.requiredPRs}</td>
38+
<td>{user.totalReviews}</td>
39+
<td>{user.remainingWeeks}</td>
40+
<td>
41+
<input
42+
className={styles['promoteCheckbox']}
43+
type="checkbox"
44+
defaultChecked={user.promote}
45+
/>
46+
</td>
47+
</tr>
48+
))}
49+
</>
50+
);
51+
}
52+
53+
function PromotionTable() {
54+
const [eligibilityData, setEligibilityData] = useState([]);
55+
const [loading, setLoading] = useState(true);
56+
const darkMode = useSelector(state => state.theme.darkMode);
57+
58+
useEffect(() => {
59+
setLoading(true);
60+
const timer = setTimeout(() => {
61+
setEligibilityData(dummyMembers);
62+
setLoading(false);
63+
}, 500);
64+
return () => clearTimeout(timer);
65+
}, []);
66+
67+
const newMembers = eligibilityData.filter(u => u.isNew);
68+
const existingMembers = eligibilityData.filter(u => !u.isNew);
69+
70+
if (loading) return <div>Loading promotions...</div>;
71+
72+
return (
73+
<div className={styles.container} data-theme={darkMode ? 'dark' : 'light'}>
74+
<div className={styles.header}>
75+
<h1>Promotion Eligibility</h1>
76+
<div className={styles.actions}>
77+
<button type="button" className={`${styles.btn} ${styles['btnPrimary']}`}>
78+
Review for This Week
79+
</button>
80+
<button type="button" className={`${styles.btn} ${styles['btnSecondary']}`}>
81+
Process Promotions
82+
</button>
83+
</div>
84+
</div>
85+
86+
<div className={styles['promotionTableWrapper']}>
87+
<table className={styles['promotionTable']}>
88+
<thead>
89+
<tr>
90+
<th style={{ width: '15%' }}>Existing/New</th>
91+
<th>Reviewer</th>
92+
<th>Weekly Requirements</th>
93+
<th>Required PRs</th>
94+
<th>Total Reviews</th>
95+
<th>Remaining Weeks</th>
96+
<th>Promote?</th>
97+
</tr>
98+
</thead>
99+
<tbody>
100+
<MemberSection title="New Members" members={newMembers} styles={styles} />
101+
<MemberSection title="Existing Members" members={existingMembers} styles={styles} />
102+
</tbody>
103+
</table>
104+
</div>
105+
</div>
106+
);
107+
}
108+
109+
export default PromotionTable;
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
.container {
2+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3+
background-color: #ffffff;
4+
border: 1px solid #e1e4e8;
5+
border-radius: 6px;
6+
width: 100%;
7+
padding: 0;
8+
margin: 0;
9+
color: #24292e;
10+
}
11+
12+
.header {
13+
display: flex;
14+
justify-content: space-between;
15+
align-items: center;
16+
padding: 16px 24px;
17+
border-bottom: 1px solid #e1e4e8;
18+
}
19+
20+
.header h1 {
21+
font-size: 24px;
22+
font-weight: 600;
23+
margin: 0;
24+
}
25+
26+
.header .actions {
27+
display: flex;
28+
gap: 8px;
29+
}
30+
31+
.btn {
32+
padding: 5px 16px;
33+
font-size: 14px;
34+
font-weight: 500;
35+
border-radius: 6px;
36+
cursor: pointer;
37+
appearance: none;
38+
-webkit-appearance: none;
39+
border: 1px solid transparent;
40+
}
41+
42+
[data-theme="light"] .btnPrimary {
43+
background-color: #22863a;
44+
color: #ffffff;
45+
border-color: #22863a;
46+
}
47+
48+
[data-theme="light"] .btnSecondary {
49+
background-color: #0d6efd;
50+
color: #ffffff;
51+
border-color: #0d6efd;
52+
}
53+
54+
.actions .btn {
55+
margin-right: 100px;
56+
}
57+
58+
.actions .btn:last-child {
59+
margin-right: 0;
60+
}
61+
62+
.promotionTableWrapper {
63+
overflow-x: auto;
64+
-webkit-overflow-scrolling: touch;
65+
}
66+
67+
.promotionTable {
68+
min-width: 700px;
69+
width: 100%;
70+
border-collapse: collapse;
71+
}
72+
73+
.promotionTable th,
74+
.promotionTable td {
75+
padding: 12px 16px;
76+
text-align: left;
77+
font-size: 14px;
78+
border-bottom: 1px solid #e1e4e8;
79+
}
80+
81+
.promotionTable thead th {
82+
background-color: #f6f8fa;
83+
font-weight: 600;
84+
}
85+
86+
.promotionTable tbody tr:last-child td {
87+
border-bottom: none;
88+
}
89+
90+
.sectionHeader td {
91+
background-color: #f6f8fa;
92+
font-weight: bold;
93+
color: #586069;
94+
}
95+
96+
.statusMet {
97+
color: #22863a;
98+
}
99+
100+
.statusNotMet {
101+
color: #cb2431;
102+
}
103+
104+
.statusIcon {
105+
font-weight: bold;
106+
margin-right: 8px;
107+
}
108+
109+
.promoteCheckbox {
110+
width: 20px;
111+
height: 20px;
112+
cursor: pointer;
113+
}
114+
115+
/* Dark Mode Support */
116+
[data-theme="dark"] {
117+
background-color: #1e2329;
118+
color: #f0f0f0;
119+
border-color: #383e44;
120+
}
121+
122+
[data-theme="dark"] .container {
123+
background-color: #1e2329;
124+
border-color: #383e44;
125+
color: #f0f0f0;
126+
}
127+
128+
[data-theme="dark"] .header {
129+
background-color: #24292f;
130+
border-bottom: 1px solid #383e44;
131+
}
132+
133+
[data-theme="dark"] .header h1 {
134+
color: #f0f0f0;
135+
}
136+
137+
[data-theme="dark"] .btnPrimary {
138+
background-color: #22863a;
139+
color: #ffffff;
140+
border-color: #22863a;
141+
}
142+
143+
[data-theme="dark"] .btnSecondary {
144+
background-color: #0d6efd;
145+
color: #ffffff;
146+
border-color: #0d6efd;
147+
}
148+
149+
[data-theme="dark"] .promotionTable {
150+
width: 100%;
151+
background-color: #2d333b;
152+
}
153+
154+
[data-theme="dark"] .promotionTable thead th {
155+
background-color: #24292f;
156+
border-color: #383e44;
157+
color: #f0f0f0;
158+
}
159+
160+
[data-theme="dark"] .promotionTable th,
161+
[data-theme="dark"] .promotionTable td {
162+
border-bottom: 1px solid #383e44;
163+
}
164+
165+
[data-theme="dark"] .sectionHeader td {
166+
background-color: #24292f;
167+
color: #f0f0f0;
168+
}
169+
170+
[data-theme="dark"] .promotionTable tbody tr.odd-row {
171+
background-color: #24292f;
172+
}
173+
174+
[data-theme="dark"] .promotionTable tbody tr:hover {
175+
background-color: #363b43;
176+
cursor: pointer;
177+
}
178+
179+
[data-theme="dark"] .promotionTable tbody tr:hover td {
180+
color: #f0f0f0;
181+
}
182+
183+
[data-theme="dark"] .statusMet {
184+
color: #53d06e;
185+
}
186+
187+
[data-theme="dark"] .statusNotMet {
188+
color: #fa7970;
189+
}
190+
191+
@media (max-width: 768px) {
192+
.header {
193+
flex-direction: column;
194+
align-items: flex-start;
195+
padding: 16px;
196+
}
197+
198+
.header .actions {
199+
flex-direction: column;
200+
width: 100%;
201+
margin-top: 16px;
202+
}
203+
204+
.actions .btn {
205+
width: 100%;
206+
margin-right: 0;
207+
margin-bottom: 8px;
208+
}
209+
}

src/routes.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ import Collaboration from './components/Collaboration';
111111
import SuggestedJobsList from './components/Collaboration/SuggestedJobsList';
112112

113113
import TestEventRegistration from './components/EventRegistration/TestEventRegistration';
114+
// Questionnaire Dashboard
114115
import MemberList from './components/QuestionnaireDashboard/MemberList';
116+
import PromotionTable from './components/QuestionnaireDashboard/PromotionTable';
115117
import EventPopularity from './components/EventPopularity/EventPopularity';
116118
import ApplicantVolunteerRatio from './components/ApplicantVolunteerRatio/ApplicantVolunteerRatio';
117119
import { JobAnalyticsCompetitiveRolesPage } from './components/Reports/JobAnalytics';
@@ -837,6 +839,7 @@ export default (
837839
<ProtectedRoute path="/userprofile/:userId" fallback component={UserProfile} />
838840
<ProtectedRoute path="/userprofileedit/:userId" component={UserProfileEdit} />
839841
<ProtectedRoute path="/updatepassword/:userId" component={UpdatePassword} />
842+
<ProtectedRoute path="/promotiontable" exact component={PromotionTable} />
840843
<ProtectedRoute path="/memberlist" exact component={MemberList} />
841844
<Route path="/Logout" component={Logout} />
842845
<Route path="/forcePasswordUpdate/:userId" component={ForcePasswordUpdate} />

0 commit comments

Comments
 (0)