Skip to content

Commit 370d925

Browse files
authored
Merge pull request #1476 from topcoder-platform/dev
[PROD RELEASE] - Fixes & Updates
2 parents bf6a11e + 9f4035a commit 370d925

16 files changed

Lines changed: 746 additions & 46 deletions

File tree

src/apps/calendar/src/lib/components/Calendar/Calendar.module.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
}
112112

113113
.cell {
114-
min-height: 72px;
114+
min-height: 60px;
115+
padding: 6px;
115116
}
116117
}

src/apps/calendar/src/lib/components/TeamCalendar/TeamCalendar.module.scss

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
}
8181

8282
.userItem {
83-
width: 100%;
8483
border-radius: 8px;
8584
padding: 8px 10px;
8685
font-weight: 700;
@@ -129,16 +128,16 @@
129128

130129
@media (max-width: 768px) {
131130
.teamCalendar {
132-
padding: 12px;
131+
padding: 5px;
133132
}
134133

135134
.grid {
136-
gap: 8px;
135+
gap: 6px;
137136
}
138137

139138
.cell {
140-
min-height: 96px;
141-
padding: 10px;
139+
min-height: 60px;
140+
padding: 6px;
142141
}
143142

144143
.userItem {
@@ -148,4 +147,89 @@
148147
.dateNumber {
149148
font-size: 15px;
150149
}
150+
151+
.cellButton {
152+
cursor: pointer;
153+
}
154+
}
155+
156+
.cellButton {
157+
all: unset;
158+
display: flex;
159+
flex-direction: column;
160+
width: 100%;
161+
height: 100%;
162+
gap: 8px;
163+
cursor: default;
164+
}
165+
166+
167+
.countBadge {
168+
align-self: center;
169+
font-size: 12px;
170+
font-weight: 800;
171+
padding: 4px 6px;
172+
border-radius: 999px;
173+
background: #acaeb3;
174+
color: #ffffff;
175+
line-height: 1;
176+
}
177+
178+
.modalRoot {
179+
position: fixed;
180+
inset: 0;
181+
z-index: 1000;
182+
display: flex;
183+
align-items: center;
184+
justify-content: center;
185+
}
186+
187+
.backdrop {
188+
position: absolute;
189+
inset: 0;
190+
background: var(--text-secondary);
191+
}
192+
193+
.popover {
194+
position: relative;
195+
z-index: 1;
196+
197+
width: calc(100vw - 32px);
198+
max-width: 420px;
199+
max-height: 70vh;
200+
overflow-y: auto;
201+
202+
background: #fff;
203+
border: 1px solid #e5e7eb;
204+
border-radius: 16px;
205+
box-shadow: 0 24px 64px $tc-black;
206+
}
207+
208+
.popoverHeader {
209+
display: flex;
210+
align-items: center;
211+
justify-content: space-between;
212+
padding: 12px 14px;
213+
border-bottom: 1px solid #e5e7eb;
214+
}
215+
216+
.popoverTitle {
217+
font-weight: 800;
218+
color: #111827;
219+
}
220+
221+
.closeBtn {
222+
all: unset;
223+
cursor: pointer;
224+
font-size: 18px;
225+
line-height: 1;
226+
padding: 6px 8px;
227+
border-radius: 8px;
228+
}
229+
230+
.popoverBody {
231+
padding: 12px 14px;
232+
display: flex;
233+
flex-direction: column;
234+
gap: 8px;
151235
}

src/apps/calendar/src/lib/components/TeamCalendar/TeamCalendar.tsx

Lines changed: 123 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { isWeekend } from 'date-fns'
2-
import { FC, useMemo } from 'react'
1+
import { format, isWeekend } from 'date-fns'
2+
import { FC, useCallback, useMemo, useState } from 'react'
33
import classNames from 'classnames'
44

55
import { LoadingSpinner } from '~/libs/ui'
6+
import { useCheckIsMobile } from '~/libs/shared'
67

78
import { LeaveStatus, TeamLeaveDate } from '../../models'
89
import { getDateKey, getMonthDates } from '../../utils'
@@ -60,6 +61,25 @@ export const TeamCalendar: FC<TeamCalendarProps> = (props: TeamCalendarProps) =>
6061
const currentDate = props.currentDate
6162
const isLoading = props.isLoading
6263
const teamLeaveDates = props.teamLeaveDates
64+
const [openDateKey, setOpenDateKey] = useState<string | undefined>(undefined)
65+
66+
const isMobile: boolean = useCheckIsMobile()
67+
68+
const closePopover = useCallback(() => {
69+
setOpenDateKey(undefined)
70+
}, [])
71+
72+
const handleCellClick = useCallback(
73+
(e: React.MouseEvent<HTMLButtonElement>) => {
74+
if (!isMobile) return
75+
76+
const dateKey = e.currentTarget.dataset.dateKey
77+
if (!dateKey) return
78+
79+
setOpenDateKey(prev => (prev === dateKey ? undefined : dateKey))
80+
},
81+
[isMobile],
82+
)
6383

6484
const monthDates = useMemo(
6585
() => getMonthDates(currentDate.getFullYear(), currentDate.getMonth()),
@@ -116,44 +136,126 @@ export const TeamCalendar: FC<TeamCalendarProps> = (props: TeamCalendarProps) =>
116136
const overflowCount = sortedUsers.length - displayedUsers.length
117137
const weekendClass = isWeekend(date) ? styles.weekend : undefined
118138

139+
// Mobile popover open/close
140+
const isOpen = openDateKey === dateKey
141+
const leaveCount = sortedUsers.length
142+
119143
return (
120144
<div
121145
key={dateKey}
122146
className={classNames(styles.cell, weekendClass, {
123147
[styles.loading]: isLoading,
148+
[styles.hasLeave]: leaveCount > 0,
124149
})}
125150
>
126-
<span className={styles.dateNumber}>{date.getDate()}</span>
127-
<div className={styles.userList}>
128-
{displayedUsers.length > 0
129-
&& displayedUsers.map((user, userIndex) => {
130-
const isHolidayStatus = user.status === LeaveStatus.WIPRO_HOLIDAY
131-
|| user.status === LeaveStatus.HOLIDAY
151+
{/* Whole cell tappable on mobile */}
152+
<button
153+
type='button'
154+
className={styles.cellButton}
155+
disabled={isLoading}
156+
onClick={handleCellClick}
157+
data-date-key={dateKey}
158+
aria-haspopup='dialog'
159+
aria-expanded={isMobile ? isOpen : undefined}
160+
aria-label={`Open leave list for ${dateKey}`}
161+
>
162+
<span className={styles.dateNumber}>{date.getDate()}</span>
163+
164+
{/* MOBILE: only show count badge */}
165+
{isMobile && leaveCount > 0 && (
166+
<span className={styles.countBadge}>{leaveCount}</span>
167+
)}
168+
169+
{/* DESKTOP: show list in the cell (your existing UI) */}
170+
{!isMobile && (
171+
<div className={styles.userList}>
172+
{displayedUsers.length > 0
173+
&& displayedUsers.map((user, userIndex) => {
174+
const isHolidayStatus
175+
= user.status === LeaveStatus.WIPRO_HOLIDAY
176+
|| user.status === LeaveStatus.HOLIDAY
177+
178+
return (
179+
<div
180+
key={`${dateKey}-${user.userId}-${userIndex.toString()}`}
181+
className={classNames(
182+
styles.userItem,
183+
isHolidayStatus
184+
? styles.userHoliday
185+
: styles.userLeave,
186+
)}
187+
>
188+
{getUserDisplayName(user)}
189+
</div>
190+
)
191+
})}
192+
{overflowCount > 0 && (
193+
<div className={styles.overflowIndicator}>
194+
{`+${overflowCount} more`}
195+
</div>
196+
)}
197+
</div>
198+
)}
199+
</button>
200+
</div>
201+
)
202+
})}
203+
</div>
204+
205+
{isMobile && openDateKey && (() => {
206+
const selectedDate = paddedDates.find(d => d && getDateKey(d) === openDateKey)
207+
if (!selectedDate) return undefined
208+
209+
const selectedEntry = teamLeaveDates.find(item => item.date === openDateKey)
210+
const selectedUsers = [...(selectedEntry?.usersOnLeave ?? [])].sort(compareUsersByName)
211+
212+
return (
213+
<div className={styles.modalRoot}>
214+
<div className={styles.backdrop} onClick={closePopover} />
215+
216+
<div className={styles.popover} role='dialog' aria-label='Users on leave'>
217+
<div className={styles.popoverHeader}>
218+
<div className={styles.popoverTitle}>
219+
{format(selectedDate, 'EEE, dd MMM yyyy')}
220+
</div>
221+
222+
<button
223+
type='button'
224+
className={styles.closeBtn}
225+
onClick={closePopover}
226+
aria-label='Close'
227+
>
228+
229+
</button>
230+
</div>
231+
232+
<div className={styles.popoverBody}>
233+
{selectedUsers.length === 0 ? (
234+
<div className={styles.emptyState}>No leave</div>
235+
) : (
236+
selectedUsers.map((user, idx) => {
237+
const isHolidayStatus
238+
= user.status === LeaveStatus.WIPRO_HOLIDAY
239+
|| user.status === LeaveStatus.HOLIDAY
132240

133241
return (
134242
<div
135-
key={`${dateKey}-${user.userId}-${userIndex.toString()}`}
243+
key={`${openDateKey}-${user.userId}-${idx.toString()}`}
136244
className={classNames(
137245
styles.userItem,
138-
isHolidayStatus
139-
? styles.userHoliday
140-
: styles.userLeave,
246+
isHolidayStatus ? styles.userHoliday : styles.userLeave,
141247
)}
142248
>
143249
{getUserDisplayName(user)}
144250
</div>
145251
)
146-
})}
147-
{overflowCount > 0 && (
148-
<div className={styles.overflowIndicator}>
149-
{`+${overflowCount} more`}
150-
</div>
252+
})
151253
)}
152254
</div>
153255
</div>
154-
)
155-
})}
156-
</div>
256+
</div>
257+
)
258+
})()}
157259

158260
{isLoading && (
159261
<div className={styles.loadingOverlay}>
@@ -162,6 +264,7 @@ export const TeamCalendar: FC<TeamCalendarProps> = (props: TeamCalendarProps) =>
162264
)}
163265
</div>
164266
)
267+
165268
}
166269

167270
export default TeamCalendar

0 commit comments

Comments
 (0)