Skip to content

Commit c816c64

Browse files
committed
Dynamic population of teams using query parameters
1 parent f0efb26 commit c816c64

File tree

8 files changed

+190
-133
lines changed

8 files changed

+190
-133
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
My teams needed a standup timer during standup meetings in order to avoid waste of time.
66
The timer can be shared with screen sharing in order to permit to all members to use the right amount of time.
77

8-
### How to customize
8+
### How to use
99

10-
It is enough to change the teams structure and members from the data folder
10+
Names can be passed in query parameters, in order to populate the team.
11+
12+
The format is
13+
14+
```
15+
lukdog.com/standup-timer&name1=time&name2=time...
16+
```
17+
18+
If time is not specified a default of 2 minutes is used
1119

1220
### How to run
1321

data/data.ts

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,11 @@
11
export type TeamMember = {
22
name : string
33
time : number
4-
role : 'TL' | 'FE Dev' | 'BE Dev' | 'Mobile Dev' | 'Designer' | 'PO' | 'DevOps' | 'QA Engineer'
4+
role : 'TL' | 'FE Dev' | 'BE Dev' | 'Mobile Dev' | 'Designer' | 'PO' | 'DevOps' | 'QA Engineer' | 'Member'
55
};
66

77
export type Team = {
88
name : string
99
members : TeamMember[]
1010
};
1111

12-
13-
export const Teams : Team[] = [
14-
{
15-
name: "Wedo",
16-
members: [
17-
{
18-
name: "Giulio",
19-
role: "BE Dev",
20-
time: 2
21-
},
22-
{
23-
name: "Matteo",
24-
role: "FE Dev",
25-
time: 2
26-
},
27-
{
28-
name: "Gianmarco",
29-
role: "FE Dev",
30-
time: 2
31-
},
32-
{
33-
name: "Chiara",
34-
role: "FE Dev",
35-
time: 2
36-
},
37-
{
38-
name: "Lia",
39-
role: "FE Dev",
40-
time: 2
41-
},
42-
{
43-
name: "Valerio",
44-
role: "Designer",
45-
time: 2
46-
},
47-
{
48-
name: "Gabriele",
49-
role: "Designer",
50-
time: 2
51-
},
52-
{
53-
name: "Massimo",
54-
role: "DevOps",
55-
time: 2
56-
},
57-
{
58-
name: "Josefine",
59-
role: "PO",
60-
time: 2
61-
},
62-
{
63-
name: "Federico",
64-
role: "QA Engineer",
65-
time: 2
66-
},
67-
68-
]
69-
},
70-
{
71-
name: "Mobile",
72-
members: [
73-
{
74-
name: "Luca",
75-
role: "TL",
76-
time: 2
77-
},
78-
{
79-
name: "Enrico",
80-
role: "Mobile Dev",
81-
time: 2
82-
},
83-
{
84-
name: "Alessio",
85-
role: "Mobile Dev",
86-
time: 2
87-
},
88-
{
89-
name: "Sara",
90-
role: "Designer",
91-
time: 2
92-
},
93-
]
94-
}
95-
]

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"classnames": "^2.5.1",
1314
"react": "^18.2.0",
1415
"react-countdown-circle-timer": "^3.2.1",
15-
"react-dom": "^18.2.0"
16+
"react-dom": "^18.2.0",
17+
"react-router-dom": "^7.6.2"
1618
},
1719
"devDependencies": {
1820
"@types/react": "^18.2.43",

src/App.tsx

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,12 @@
1-
import { useState } from 'react';
2-
import { Teams, TeamMember } from '../data/data.ts';
3-
import StandupTimer from './components/StandupTimer';
4-
import TeamChoice from './components/TeamChoice';
5-
import Page from './components/Page/index.tsx';
6-
7-
function shuffle(array: TeamMember[]) {
8-
let currentIndex = array.length,
9-
randomIndex;
10-
11-
// While there remain elements to shuffle.
12-
while (currentIndex > 0) {
13-
// Pick a remaining element.
14-
randomIndex = Math.floor(Math.random() * currentIndex);
15-
currentIndex--;
16-
17-
// And swap it with the current element.
18-
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
19-
}
20-
21-
return array;
22-
}
1+
import { BrowserRouter } from 'react-router-dom';
2+
import StandupIntro from './components/StandupIntro/index.tsx';
233

244
function App() {
25-
const [selectedTeam, setSelectedTeam] = useState(-1);
26-
27-
const selectTeam = (team: number) => {
28-
team != -1 && (Teams[team].members = shuffle(Teams[team].members));
29-
setSelectedTeam(team);
30-
};
31-
325
return (
336
<div className="App">
34-
{selectedTeam >= 0 && (
35-
<Page title={Teams[selectedTeam].name + ' Standup'} onClose={() => selectTeam(-1)}>
36-
<StandupTimer members={Teams[selectedTeam].members} />
37-
</Page>
38-
)}
39-
{selectedTeam < 0 && (
40-
<Page title="Teams">
41-
<TeamChoice teams={Teams} onTeamSelected={(team) => selectTeam(team)} />
42-
</Page>
43-
)}
7+
<BrowserRouter>
8+
<StandupIntro />
9+
</BrowserRouter>
4410
</div>
4511
);
4612
}

src/components/Card/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
import classNames from 'classnames';
2+
13
type CardProps = {
24
children?: JSX.Element[] | JSX.Element;
5+
className?: string;
36
};
47

5-
export default function Card({ children }: CardProps) {
8+
export default function Card({ children, className }: CardProps) {
69
return (
7-
<div className="bg-white dark:border-gray-800 relative z-10 mx-4 rounded-md border p-4 shadow-xl dark:bg-card-dark">
10+
<div
11+
className={classNames(
12+
'relative z-10 mx-4 rounded-md border bg-white p-4 shadow-xl dark:border-gray-800 dark:bg-card-dark',
13+
className
14+
)}
15+
>
816
{children}
917
</div>
1018
);
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useSearchParams } from 'react-router-dom';
2+
import { TeamMember } from '../../../data/data.ts';
3+
4+
import StandupTimer from '../StandupTimer';
5+
import Page from '../Page';
6+
7+
import Card from '../Card';
8+
import { useState } from 'react';
9+
10+
export default function StandupIntro() {
11+
const [standupStarted, setStandupStarted] = useState(false);
12+
const [searchParams] = useSearchParams();
13+
const [members, setMembers] = useState<TeamMember[]>(parseMembersFromParams());
14+
console.log(searchParams);
15+
16+
function shuffle(array: TeamMember[]): TeamMember[] {
17+
let currentIndex = array.length,
18+
randomIndex;
19+
20+
// While there remain elements to shuffle.
21+
while (currentIndex > 0) {
22+
// Pick a remaining element.
23+
randomIndex = Math.floor(Math.random() * currentIndex);
24+
currentIndex--;
25+
26+
// And swap it with the current element.
27+
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
28+
}
29+
30+
return array;
31+
}
32+
33+
function calculateTotalTime(members: TeamMember[]): number {
34+
return members.reduce((total, member) => total + member.time, 0);
35+
}
36+
37+
function removeMemberbyIndex(index: number): void {
38+
// Remove the member at the specified index and set the new members state
39+
const newMembers = members.filter((_, i) => i !== index);
40+
setMembers(newMembers);
41+
}
42+
43+
function parseMembersFromParams(): TeamMember[] {
44+
// members in query params are in the format: &name1=time1&name2=time2,...
45+
const params = [];
46+
47+
for (const entry of searchParams.entries()) {
48+
params.push(entry);
49+
}
50+
51+
const m: TeamMember[] = params.map((param) => {
52+
const [name, time] = param;
53+
return {
54+
name: name[0].toUpperCase() + name.substring(1),
55+
role: 'Member',
56+
time: parseInt(time, 10) || 2,
57+
};
58+
});
59+
60+
return shuffle(m);
61+
}
62+
63+
return (
64+
<>
65+
{standupStarted && (
66+
<Page title="Daily Standup" onClose={() => setStandupStarted(false)}>
67+
<StandupTimer members={members} />
68+
</Page>
69+
)}
70+
71+
{!standupStarted && (
72+
<Page title="Daily Standup">
73+
<div className="mx-auto grid max-w-2xl py-4">
74+
<Card className="mb-10">
75+
<h1 className="text-center text-3xl font-semibold dark:text-text-dark">Total Time</h1>
76+
<p className="text-center text-xl text-gray-500 dark:text-text-dark">
77+
{calculateTotalTime(members) + 'm'}
78+
</p>
79+
<h1
80+
onClick={setStandupStarted.bind(null, true)}
81+
className="mx-auto mt-3 w-fit cursor-pointer rounded-md bg-orange-600 px-2 py-1 text-center text-2xl font-bold text-white dark:text-text-dark"
82+
>
83+
Start
84+
</h1>
85+
</Card>
86+
87+
{members.map((member, index) => (
88+
<div key={index}>
89+
<Card>
90+
<h1 className="text-center text-xl font-semibold dark:text-text-dark">{member.name}</h1>
91+
<p className="text-center text-gray-500 dark:text-text-dark">{member.time + 'm'}</p>
92+
<h1
93+
onClick={() => removeMemberbyIndex(index)}
94+
className="absolute right-4 top-6 mx-2 w-fit cursor-pointer rounded-md bg-orange-600 px-2 py-1 text-center text-xl font-bold text-white dark:text-text-dark"
95+
>
96+
X
97+
</h1>
98+
</Card>
99+
{index != members.length - 1 && (
100+
<div className="divider-container -mt-2 flex flex-col items-center">
101+
<div className="relative z-10 h-4 w-4 rounded-full bg-orange-600 dark:brightness-75"></div>
102+
<div className="-mt-2 h-5 w-1 rounded-full bg-gray-200 dark:bg-gray-500"></div>
103+
</div>
104+
)}
105+
</div>
106+
))}
107+
</div>
108+
</Page>
109+
)}
110+
</>
111+
);
112+
}

src/components/StandupTimer/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ interface StandupTimerProps {
99

1010
export default function StandupTimer({ members }: StandupTimerProps) {
1111
const [currentMember, setCurrentMember] = useState(0);
12-
const [completed, setCompleted] = useState(false);
12+
const [completed, setCompleted] = useState(members.length === 0);
1313

1414
const onNext = () => {
1515
if (currentMember < members.length - 1) {

0 commit comments

Comments
 (0)