Skip to content

Commit f8df1b6

Browse files
authored
Bulk edit shirt sizes + size totals (#15382)
* bulk edit t-shirt info * future team members, shirt total counts * t-shirt filters
1 parent 3e2a7c9 commit f8df1b6

File tree

3 files changed

+363
-81
lines changed

3 files changed

+363
-81
lines changed

src/hooks/useTeamMembers.ts

Lines changed: 109 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,106 +20,151 @@ export type TeamMember = {
2020
tShirtAdditionalInfo: string | null
2121
}
2222

23+
function mapProfile(m: any): TeamMember {
24+
return {
25+
id: m.id,
26+
avatarUrl: m.attributes?.avatar?.data?.attributes?.url || null,
27+
color: m.attributes?.color || null,
28+
firstName: m.attributes?.firstName || null,
29+
lastName: m.attributes?.lastName || null,
30+
companyRole: m.attributes?.companyRole || null,
31+
location: m.attributes?.location || null,
32+
country: m.attributes?.country || null,
33+
teams: (m.attributes?.teams?.data || []).map((t: any) => t.attributes?.name),
34+
leadsTeams: (m.attributes?.leadTeams?.data || []).map((t: any) => t.attributes?.name),
35+
pineappleOnPizza: m.attributes?.pineappleOnPizza ?? null,
36+
startDate: m.attributes?.startDate || null,
37+
tShirtFit: m.attributes?.tShirt?.fit || null,
38+
tShirtSize: m.attributes?.tShirt?.size || null,
39+
tShirtAdditionalInfo: m.attributes?.tShirt?.additionalInfo || null,
40+
}
41+
}
42+
43+
async function fetchPaginated(
44+
url: string,
45+
headers: HeadersInit,
46+
filters: Record<string, any>,
47+
populate: Record<string, any>,
48+
sort: string[]
49+
): Promise<any[] | null> {
50+
const allData: any[] = []
51+
let page = 1
52+
let pageCount = 1
53+
54+
while (page <= pageCount) {
55+
const query = qs.stringify(
56+
{ populate, filters, pagination: { page, pageSize: 100 }, sort },
57+
{ encodeValuesOnly: true }
58+
)
59+
const res = await fetch(`${url}?${query}`, { headers })
60+
if (!res.ok) return null
61+
62+
const { data, meta } = await res.json()
63+
if (!data) return null
64+
65+
allData.push(...data)
66+
pageCount = meta?.pagination?.pageCount || 1
67+
page++
68+
}
69+
70+
return allData
71+
}
72+
2373
export function useTeamMembers() {
2474
const { getJwt } = useUser()
2575
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([])
76+
const [futureJoiners, setFutureJoiners] = useState<TeamMember[]>([])
2677
const [loading, setLoading] = useState(true)
2778

2879
useEffect(() => {
29-
const fetchTeamMembers = async () => {
80+
const fetchAll = async () => {
3081
try {
3182
const token = await getJwt()
3283
const headers: HeadersInit = token ? { Authorization: `Bearer ${token}` } : {}
33-
34-
const allData: any[] = []
35-
let page = 1
36-
let pageCount = 1
37-
38-
while (page <= pageCount) {
39-
const query = qs.stringify(
40-
{
41-
populate: {
42-
avatar: { fields: ['url'] },
43-
teams: { fields: ['name'] },
44-
leadTeams: { fields: ['name'] },
45-
tShirt: true,
46-
},
47-
filters: {
48-
teams: { id: { $notNull: true } },
49-
id: { $ne: 28378 },
50-
},
51-
pagination: { page, pageSize: 100 },
52-
sort: ['startDate:asc'],
53-
},
54-
{ encodeValuesOnly: true }
55-
)
56-
57-
const res = await fetch(`${process.env.GATSBY_SQUEAK_API_HOST}/api/profiles?${query}`, { headers })
58-
if (!res.ok) {
59-
console.error('Failed to fetch team members', res.status)
60-
setLoading(false)
61-
return
62-
}
63-
64-
const { data, meta } = await res.json()
65-
if (!data) {
66-
setLoading(false)
67-
return
68-
}
69-
70-
allData.push(...data)
71-
pageCount = meta?.pagination?.pageCount || 1
72-
page++
84+
const apiUrl = `${process.env.GATSBY_SQUEAK_API_HOST}/api/profiles`
85+
const populate = {
86+
avatar: { fields: ['url'] },
87+
teams: { fields: ['name'] },
88+
leadTeams: { fields: ['name'] },
89+
tShirt: true,
7390
}
7491

75-
const members: TeamMember[] = allData.map((m: any) => ({
76-
id: m.id,
77-
avatarUrl: m.attributes?.avatar?.data?.attributes?.url || null,
78-
color: m.attributes?.color || null,
79-
firstName: m.attributes?.firstName || null,
80-
lastName: m.attributes?.lastName || null,
81-
companyRole: m.attributes?.companyRole || null,
82-
location: m.attributes?.location || null,
83-
country: m.attributes?.country || null,
84-
teams: (m.attributes?.teams?.data || []).map((t: any) => t.attributes?.name),
85-
leadsTeams: (m.attributes?.leadTeams?.data || []).map((t: any) => t.attributes?.name),
86-
pineappleOnPizza: m.attributes?.pineappleOnPizza ?? null,
87-
startDate: m.attributes?.startDate || null,
88-
tShirtFit: m.attributes?.tShirt?.fit || null,
89-
tShirtSize: m.attributes?.tShirt?.size || null,
90-
tShirtAdditionalInfo: m.attributes?.tShirt?.additionalInfo || null,
91-
}))
92+
const today = new Date().toISOString().split('T')[0]
93+
const isFuture = (m: TeamMember) => m.startDate != null && m.startDate > today
94+
95+
const [teamData, futureData] = await Promise.all([
96+
fetchPaginated(
97+
apiUrl,
98+
headers,
99+
{ teams: { id: { $notNull: true } }, id: { $ne: 28378 } },
100+
populate,
101+
['startDate:asc']
102+
),
103+
fetchPaginated(apiUrl, headers, { startDate: { $gt: today }, id: { $ne: 28378 } }, populate, [
104+
'startDate:asc',
105+
]),
106+
])
107+
108+
const allWithTeams = (teamData || []).map(mapProfile)
109+
const members = allWithTeams.filter((m) => !isFuture(m))
110+
111+
const futureFromTeams = allWithTeams.filter(isFuture)
112+
const futureWithoutTeams = (futureData || []).map(mapProfile)
113+
const seenIds = new Set(futureFromTeams.map((m) => m.id))
114+
const joiners = [...futureFromTeams, ...futureWithoutTeams.filter((m) => !seenIds.has(m.id))]
92115

93116
setTeamMembers(members)
117+
setFutureJoiners(joiners)
94118
setLoading(false)
95119
} catch (err) {
96120
console.error('Failed to fetch team members', err)
97121
setLoading(false)
98122
}
99123
}
100124

101-
fetchTeamMembers()
125+
fetchAll()
102126
}, [])
103127

104-
const updateProfile = async (profileId: number, updates: Partial<TeamMember>) => {
128+
const updateProfile = async (profileId: number, updates: Partial<TeamMember>): Promise<boolean> => {
105129
const token = await getJwt()
106-
if (!token) return
130+
if (!token) return false
107131

132+
const current = teamMembers.find((m) => m.id === profileId) || futureJoiners.find((m) => m.id === profileId)
108133
setTeamMembers((prev) => prev.map((m) => (m.id === profileId ? { ...m, ...updates } : m)))
134+
setFutureJoiners((prev) => prev.map((m) => (m.id === profileId ? { ...m, ...updates } : m)))
135+
136+
const apiData: Record<string, any> = {}
137+
const hasTShirtField = 'tShirtFit' in updates || 'tShirtSize' in updates || 'tShirtAdditionalInfo' in updates
138+
if (hasTShirtField) {
139+
apiData.tShirt = {
140+
fit: current?.tShirtFit ?? null,
141+
size: current?.tShirtSize ?? null,
142+
additionalInfo: current?.tShirtAdditionalInfo ?? null,
143+
}
144+
if ('tShirtFit' in updates) apiData.tShirt.fit = updates.tShirtFit
145+
if ('tShirtSize' in updates) apiData.tShirt.size = updates.tShirtSize
146+
if ('tShirtAdditionalInfo' in updates) apiData.tShirt.additionalInfo = updates.tShirtAdditionalInfo
147+
}
148+
for (const [key, value] of Object.entries(updates)) {
149+
if (key === 'tShirtFit' || key === 'tShirtSize' || key === 'tShirtAdditionalInfo') continue
150+
apiData[key] = value
151+
}
109152

110153
try {
111-
await fetch(`${process.env.GATSBY_SQUEAK_API_HOST}/api/profiles/${profileId}`, {
154+
const res = await fetch(`${process.env.GATSBY_SQUEAK_API_HOST}/api/profiles/${profileId}`, {
112155
method: 'PUT',
113-
body: JSON.stringify({ data: updates }),
156+
body: JSON.stringify({ data: apiData }),
114157
headers: {
115158
'Content-Type': 'application/json',
116159
Authorization: `Bearer ${token}`,
117160
},
118161
})
162+
return res.ok
119163
} catch (err) {
120164
console.error('Failed to update profile', err)
165+
return false
121166
}
122167
}
123168

124-
return { teamMembers, loading, updateProfile }
169+
return { teamMembers, futureJoiners, loading, updateProfile }
125170
}

src/pages/community/profiles/[id].tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ function convertCentimetersToInches(centimeters: number): number {
541541
return centimeters / 2.54
542542
}
543543

544+
// Also defined in src/pages/team-directory.tsx — update both if changed
544545
const unisexSizes = ['XS', 'S', 'M', 'L', 'XL', '2XL']
545546
const femaleSizes = ['S', 'M', 'L', 'XL', '2XL', '3XL']
546547

0 commit comments

Comments
 (0)