Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
ea3371ba
Commit
ea3371ba
authored
Dec 28, 2025
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: add ActivityCalendar components
parent
78aa4133
Changes
12
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
212 additions
and
169 deletions
+212
-169
CalendarHeader.tsx
web/src/components/ActivityCalendar/CalendarHeader.tsx
+73
-0
CalendarPopover.tsx
web/src/components/ActivityCalendar/CalendarPopover.tsx
+32
-0
MonthCard.tsx
web/src/components/ActivityCalendar/MonthCard.tsx
+17
-0
constants.ts
web/src/components/ActivityCalendar/constants.ts
+3
-0
index.ts
web/src/components/ActivityCalendar/index.ts
+5
-0
types.ts
web/src/components/ActivityCalendar/types.ts
+16
-0
Navigation.tsx
web/src/components/Navigation.tsx
+2
-8
MonthNavigator.tsx
web/src/components/StatisticsView/MonthNavigator.tsx
+41
-7
calendar-utils.ts
web/src/lib/calendar-utils.ts
+23
-0
Calendar.tsx
web/src/pages/Calendar.tsx
+0
-144
index.tsx
web/src/router/index.tsx
+0
-9
routes.ts
web/src/router/routes.ts
+0
-1
No files found.
web/src/components/ActivityCalendar/CalendarHeader.tsx
0 → 100644
View file @
ea3371ba
import
{
ChevronLeftIcon
,
ChevronRightIcon
}
from
"lucide-react"
;
import
{
useMemo
}
from
"react"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
interface
CalendarHeaderProps
{
selectedYear
:
number
;
onYearChange
:
(
year
:
number
)
=>
void
;
canGoPrev
:
boolean
;
canGoNext
:
boolean
;
}
export
const
CalendarHeader
=
({
selectedYear
,
onYearChange
,
canGoPrev
,
canGoNext
}:
CalendarHeaderProps
)
=>
{
const
t
=
useTranslate
();
const
currentYear
=
useMemo
(()
=>
new
Date
().
getFullYear
(),
[]);
const
isCurrentYear
=
selectedYear
===
currentYear
;
const
handlePrevYear
=
()
=>
{
if
(
canGoPrev
)
{
onYearChange
(
selectedYear
-
1
);
}
};
const
handleNextYear
=
()
=>
{
if
(
canGoNext
)
{
onYearChange
(
selectedYear
+
1
);
}
};
const
handleToday
=
()
=>
{
onYearChange
(
currentYear
);
};
return
(
<
div
className=
"flex items-center justify-between pb-2"
>
<
h2
className=
"text-2xl font-bold text-foreground tracking-tight leading-none"
>
{
selectedYear
}
</
h2
>
<
div
className=
"inline-flex items-center gap-2 shrink-0"
>
<
Button
variant=
"ghost"
size=
"icon"
onClick=
{
handlePrevYear
}
disabled=
{
!
canGoPrev
}
aria
-
label=
"Previous year"
className=
"rounded-full hover:bg-accent/50 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
<
ChevronLeftIcon
/>
</
Button
>
<
Button
variant=
{
isCurrentYear
?
"secondary"
:
"ghost"
}
onClick=
{
handleToday
}
disabled=
{
isCurrentYear
}
aria
-
label=
{
t
(
"common.today"
)
}
className=
"bg-accent text-accent-foreground hover:bg-accent/50 text-muted-foreground hover:text-foreground h-9 px-4 rounded-full font-medium text-sm transition-colors cursor-default"
>
{
t
(
"common.today"
)
}
</
Button
>
<
Button
variant=
"ghost"
size=
"icon"
onClick=
{
handleNextYear
}
disabled=
{
!
canGoNext
}
aria
-
label=
"Next year"
className=
"rounded-full hover:bg-accent/50 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
<
ChevronRightIcon
/>
</
Button
>
</
div
>
</
div
>
);
};
web/src/components/ActivityCalendar/CalendarPopover.tsx
0 → 100644
View file @
ea3371ba
import
{
useMemo
}
from
"react"
;
import
{
calculateYearMaxCount
,
filterDataByYear
,
generateMonthsForYear
}
from
"@/components/ActivityCalendar"
;
import
{
TooltipProvider
}
from
"@/components/ui/tooltip"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
CalendarHeader
}
from
"./CalendarHeader"
;
import
{
getMaxYear
,
MIN_YEAR
}
from
"./constants"
;
import
{
MonthCard
}
from
"./MonthCard"
;
import
type
{
CalendarPopoverProps
}
from
"./types"
;
export
const
CalendarPopover
=
({
selectedYear
,
data
,
onYearChange
,
onDateClick
,
className
}:
CalendarPopoverProps
)
=>
{
const
yearData
=
useMemo
(()
=>
filterDataByYear
(
data
,
selectedYear
),
[
data
,
selectedYear
]);
const
months
=
useMemo
(()
=>
generateMonthsForYear
(
selectedYear
),
[
selectedYear
]);
const
yearMaxCount
=
useMemo
(()
=>
calculateYearMaxCount
(
yearData
),
[
yearData
]);
const
canGoPrev
=
selectedYear
>
MIN_YEAR
;
const
canGoNext
=
selectedYear
<
getMaxYear
();
return
(
<
div
className=
{
cn
(
"w-full max-w-4xl flex flex-col gap-3 p-3"
,
className
)
}
>
<
CalendarHeader
selectedYear=
{
selectedYear
}
onYearChange=
{
onYearChange
}
canGoPrev=
{
canGoPrev
}
canGoNext=
{
canGoNext
}
/>
<
TooltipProvider
>
<
div
className=
"w-full animate-fade-in"
>
<
div
className=
"grid gap-2 sm:gap-2.5 md:gap-3 lg:gap-3 grid-cols-2 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4"
>
{
months
.
map
((
month
)
=>
(
<
MonthCard
key=
{
month
}
month=
{
month
}
data=
{
yearData
}
maxCount=
{
yearMaxCount
}
onClick=
{
onDateClick
}
/>
))
}
</
div
>
</
div
>
</
TooltipProvider
>
</
div
>
);
};
web/src/components/ActivityCalendar/MonthCard.tsx
0 → 100644
View file @
ea3371ba
import
{
CompactMonthCalendar
,
getMonthLabel
}
from
"@/components/ActivityCalendar"
;
import
{
cn
}
from
"@/lib/utils"
;
import
type
{
MonthCardProps
}
from
"./types"
;
export
const
MonthCard
=
({
month
,
data
,
maxCount
,
onClick
,
className
}:
MonthCardProps
)
=>
{
return
(
<
div
className=
{
cn
(
"flex flex-col gap-1 sm:gap-1.5 rounded-lg border bg-card p-1.5 sm:p-2 md:p-2.5 shadow-sm hover:shadow-md hover:border-border/60 transition-all duration-200"
,
className
,
)
}
>
<
div
className=
"text-xs font-semibold text-foreground text-center tracking-tight"
>
{
getMonthLabel
(
month
)
}
</
div
>
<
CompactMonthCalendar
month=
{
month
}
data=
{
data
}
maxCount=
{
maxCount
}
size=
"small"
onClick=
{
onClick
}
/>
</
div
>
);
};
web/src/components/ActivityCalendar/constants.ts
View file @
ea3371ba
...
@@ -3,6 +3,9 @@ export const MONTHS_IN_YEAR = 12;
...
@@ -3,6 +3,9 @@ export const MONTHS_IN_YEAR = 12;
export
const
WEEKEND_DAYS
=
[
0
,
6
]
as
const
;
export
const
WEEKEND_DAYS
=
[
0
,
6
]
as
const
;
export
const
MIN_COUNT
=
1
;
export
const
MIN_COUNT
=
1
;
export
const
MIN_YEAR
=
2000
;
export
const
getMaxYear
=
()
=>
new
Date
().
getFullYear
()
+
1
;
export
const
INTENSITY_THRESHOLDS
=
{
export
const
INTENSITY_THRESHOLDS
=
{
HIGH
:
0.75
,
HIGH
:
0.75
,
MEDIUM
:
0.5
,
MEDIUM
:
0.5
,
...
...
web/src/components/ActivityCalendar/index.ts
View file @
ea3371ba
export
{
ActivityCalendar
as
default
}
from
"./ActivityCalendar"
;
export
{
ActivityCalendar
as
default
}
from
"./ActivityCalendar"
;
export
{
CalendarCell
,
type
CalendarCellProps
}
from
"./CalendarCell"
;
export
{
CalendarCell
,
type
CalendarCellProps
}
from
"./CalendarCell"
;
export
{
CalendarHeader
}
from
"./CalendarHeader"
;
export
{
CalendarPopover
}
from
"./CalendarPopover"
;
export
{
CompactMonthCalendar
}
from
"./CompactMonthCalendar"
;
export
{
CompactMonthCalendar
}
from
"./CompactMonthCalendar"
;
export
*
from
"./constants"
;
export
*
from
"./constants"
;
export
{
MonthCard
}
from
"./MonthCard"
;
export
{
getTooltipText
,
type
TranslateFunction
,
useTodayDate
,
useWeekdayLabels
}
from
"./shared"
;
export
{
getTooltipText
,
type
TranslateFunction
,
useTodayDate
,
useWeekdayLabels
}
from
"./shared"
;
export
type
{
export
type
{
CalendarDayCell
,
CalendarDayCell
,
CalendarDayRow
,
CalendarDayRow
,
CalendarMatrixResult
,
CalendarMatrixResult
,
CalendarPopoverProps
,
CalendarSize
,
CalendarSize
,
CompactMonthCalendarProps
,
CompactMonthCalendarProps
,
MonthCardProps
,
}
from
"./types"
;
}
from
"./types"
;
export
{
type
UseCalendarMatrixParams
,
useCalendarMatrix
}
from
"./useCalendarMatrix"
;
export
{
type
UseCalendarMatrixParams
,
useCalendarMatrix
}
from
"./useCalendarMatrix"
;
export
{
export
{
...
...
web/src/components/ActivityCalendar/types.ts
View file @
ea3371ba
...
@@ -27,3 +27,19 @@ export interface CompactMonthCalendarProps {
...
@@ -27,3 +27,19 @@ export interface CompactMonthCalendarProps {
size
?:
CalendarSize
;
size
?:
CalendarSize
;
onClick
?:
(
date
:
string
)
=>
void
;
onClick
?:
(
date
:
string
)
=>
void
;
}
}
export
interface
MonthCardProps
{
month
:
string
;
data
:
Record
<
string
,
number
>
;
maxCount
:
number
;
onClick
?:
(
date
:
string
)
=>
void
;
className
?:
string
;
}
export
interface
CalendarPopoverProps
{
selectedYear
:
number
;
data
:
Record
<
string
,
number
>
;
onYearChange
:
(
year
:
number
)
=>
void
;
onDateClick
:
(
date
:
string
)
=>
void
;
className
?:
string
;
}
web/src/components/Navigation.tsx
View file @
ea3371ba
import
{
BellIcon
,
CalendarIcon
,
EarthIcon
,
LibraryIcon
,
PaperclipIcon
,
UserCircleIcon
}
from
"lucide-react"
;
import
{
BellIcon
,
EarthIcon
,
LibraryIcon
,
PaperclipIcon
,
UserCircleIcon
}
from
"lucide-react"
;
import
{
NavLink
}
from
"react-router-dom"
;
import
{
NavLink
}
from
"react-router-dom"
;
import
{
Tooltip
,
TooltipContent
,
TooltipProvider
,
TooltipTrigger
}
from
"@/components/ui/tooltip"
;
import
{
Tooltip
,
TooltipContent
,
TooltipProvider
,
TooltipTrigger
}
from
"@/components/ui/tooltip"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
...
@@ -34,12 +34,6 @@ const Navigation = (props: Props) => {
...
@@ -34,12 +34,6 @@ const Navigation = (props: Props) => {
title
:
t
(
"common.memos"
),
title
:
t
(
"common.memos"
),
icon
:
<
LibraryIcon
className=
"w-6 h-auto shrink-0"
/>,
icon
:
<
LibraryIcon
className=
"w-6 h-auto shrink-0"
/>,
};
};
const
calendarNavLink
:
NavLinkItem
=
{
id
:
"header-calendar"
,
path
:
Routes
.
CALENDAR
,
title
:
t
(
"common.calendar"
),
icon
:
<
CalendarIcon
className=
"w-6 h-auto shrink-0"
/>,
};
const
exploreNavLink
:
NavLinkItem
=
{
const
exploreNavLink
:
NavLinkItem
=
{
id
:
"header-explore"
,
id
:
"header-explore"
,
path
:
Routes
.
EXPLORE
,
path
:
Routes
.
EXPLORE
,
...
@@ -76,7 +70,7 @@ const Navigation = (props: Props) => {
...
@@ -76,7 +70,7 @@ const Navigation = (props: Props) => {
};
};
const
navLinks
:
NavLinkItem
[]
=
currentUser
const
navLinks
:
NavLinkItem
[]
=
currentUser
?
[
homeNavLink
,
calendarNavLink
,
exploreNavLink
,
attachmentsNavLink
,
inboxNavLink
]
?
[
homeNavLink
,
exploreNavLink
,
attachmentsNavLink
,
inboxNavLink
]
:
[
exploreNavLink
,
signInNavLink
];
:
[
exploreNavLink
,
signInNavLink
];
return
(
return
(
...
...
web/src/components/StatisticsView/MonthNavigator.tsx
View file @
ea3371ba
import
dayjs
from
"dayjs"
;
import
{
ChevronLeftIcon
,
ChevronRightIcon
}
from
"lucide-react"
;
import
{
ChevronLeftIcon
,
ChevronRightIcon
}
from
"lucide-react"
;
import
{
useState
}
from
"react"
;
import
{
CalendarPopover
}
from
"@/components/ActivityCalendar"
;
import
{
Popover
,
PopoverContent
,
PopoverTrigger
}
from
"@/components/ui/popover"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
useFilteredMemoStats
}
from
"@/hooks/useFilteredMemoStats"
;
import
i18n
from
"@/i18n"
;
import
i18n
from
"@/i18n"
;
import
{
addMonths
,
formatMonth
,
getMonthFromDate
,
getYearFromDate
,
setYearAndMonth
}
from
"@/lib/calendar-utils"
;
import
type
{
MonthNavigatorProps
}
from
"@/types/statistics"
;
import
type
{
MonthNavigatorProps
}
from
"@/types/statistics"
;
export
const
MonthNavigator
=
({
visibleMonth
,
onMonthChange
}:
MonthNavigatorProps
)
=>
{
export
const
MonthNavigator
=
({
visibleMonth
,
onMonthChange
}:
MonthNavigatorProps
)
=>
{
const
currentMonth
=
dayjs
(
visibleMonth
).
toDate
();
const
currentUser
=
useCurrentUser
();
const
[
isOpen
,
setIsOpen
]
=
useState
(
false
);
const
currentMonth
=
new
Date
(
visibleMonth
);
const
currentYear
=
getYearFromDate
(
visibleMonth
);
const
currentMonthNum
=
getMonthFromDate
(
visibleMonth
);
const
{
statistics
}
=
useFilteredMemoStats
({
userName
:
currentUser
?.
name
,
});
const
handlePrevMonth
=
()
=>
{
const
handlePrevMonth
=
()
=>
{
onMonthChange
(
dayjs
(
visibleMonth
).
subtract
(
1
,
"month"
).
format
(
"YYYY-MM"
));
onMonthChange
(
addMonths
(
visibleMonth
,
-
1
));
};
};
const
handleNextMonth
=
()
=>
{
const
handleNextMonth
=
()
=>
{
onMonthChange
(
dayjs
(
visibleMonth
).
add
(
1
,
"month"
).
format
(
"YYYY-MM"
));
onMonthChange
(
addMonths
(
visibleMonth
,
1
));
};
const
handleDateClick
=
(
date
:
string
)
=>
{
onMonthChange
(
formatMonth
(
date
));
setIsOpen
(
false
);
};
const
handleYearChange
=
(
year
:
number
)
=>
{
onMonthChange
(
setYearAndMonth
(
year
,
currentMonthNum
));
};
};
return
(
return
(
<
div
className=
"w-full mb-1 flex flex-row justify-between items-center gap-1"
>
<
div
className=
"w-full mb-1 flex flex-row justify-between items-center gap-1"
>
<
span
className=
"relative text-sm text-muted-foreground"
>
<
Popover
open=
{
isOpen
}
onOpenChange=
{
setIsOpen
}
>
<
PopoverTrigger
asChild
>
<
span
className=
"relative text-sm text-muted-foreground cursor-pointer hover:text-foreground transition-colors"
>
{
currentMonth
.
toLocaleString
(
i18n
.
language
,
{
year
:
"numeric"
,
month
:
"long"
})
}
{
currentMonth
.
toLocaleString
(
i18n
.
language
,
{
year
:
"numeric"
,
month
:
"long"
})
}
</
span
>
</
span
>
</
PopoverTrigger
>
<
PopoverContent
className=
"p-0"
align=
"start"
>
<
CalendarPopover
selectedYear=
{
currentYear
}
data=
{
statistics
.
activityStats
}
onYearChange=
{
handleYearChange
}
onDateClick=
{
handleDateClick
}
/>
</
PopoverContent
>
</
Popover
>
<
div
className=
"flex justify-end items-center shrink-0 gap-1"
>
<
div
className=
"flex justify-end items-center shrink-0 gap-1"
>
<
button
className=
"cursor-pointer hover:opacity-80 transition-opacity"
onClick=
{
handlePrevMonth
}
aria
-
label=
"Previous month"
>
<
button
className=
"cursor-pointer hover:opacity-80 transition-opacity"
onClick=
{
handlePrevMonth
}
aria
-
label=
"Previous month"
>
<
ChevronLeftIcon
className=
"w-5 h-auto shrink-0 opacity-40"
/>
<
ChevronLeftIcon
className=
"w-5 h-auto shrink-0 opacity-40"
/>
...
...
web/src/lib/calendar-utils.ts
0 → 100644
View file @
ea3371ba
import
dayjs
from
"dayjs"
;
export
const
MONTH_DATE_FORMAT
=
"YYYY-MM"
as
const
;
export
const
formatMonth
=
(
date
:
Date
|
string
):
string
=>
{
return
dayjs
(
date
).
format
(
MONTH_DATE_FORMAT
);
};
export
const
getYearFromDate
=
(
date
:
Date
|
string
):
number
=>
{
return
dayjs
(
date
).
year
();
};
export
const
getMonthFromDate
=
(
date
:
Date
|
string
):
number
=>
{
return
dayjs
(
date
).
month
();
};
export
const
addMonths
=
(
date
:
Date
|
string
,
count
:
number
):
string
=>
{
return
dayjs
(
date
).
add
(
count
,
"month"
).
format
(
MONTH_DATE_FORMAT
);
};
export
const
setYearAndMonth
=
(
year
:
number
,
month
:
number
):
string
=>
{
return
dayjs
().
year
(
year
).
month
(
month
).
format
(
MONTH_DATE_FORMAT
);
};
web/src/pages/Calendar.tsx
deleted
100644 → 0
View file @
78aa4133
import
{
ChevronLeftIcon
,
ChevronRightIcon
}
from
"lucide-react"
;
import
{
useMemo
,
useState
}
from
"react"
;
import
{
CompactMonthCalendar
,
calculateYearMaxCount
,
filterDataByYear
,
generateMonthsForYear
,
getMonthLabel
,
}
from
"@/components/ActivityCalendar"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
TooltipProvider
}
from
"@/components/ui/tooltip"
;
import
{
useDateFilterNavigation
}
from
"@/hooks"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
useFilteredMemoStats
}
from
"@/hooks/useFilteredMemoStats"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
const
MIN_YEAR
=
2000
;
const
MAX_YEAR
=
new
Date
().
getFullYear
()
+
1
;
const
Calendar
=
()
=>
{
const
currentUser
=
useCurrentUser
();
const
t
=
useTranslate
();
const
navigateToDateFilter
=
useDateFilterNavigation
();
const
[
selectedYear
,
setSelectedYear
]
=
useState
(
new
Date
().
getFullYear
());
const
{
statistics
,
loading
}
=
useFilteredMemoStats
({
userName
:
currentUser
?.
name
,
});
const
yearData
=
useMemo
(()
=>
filterDataByYear
(
statistics
.
activityStats
,
selectedYear
),
[
statistics
.
activityStats
,
selectedYear
]);
const
months
=
useMemo
(()
=>
generateMonthsForYear
(
selectedYear
),
[
selectedYear
]);
const
yearMaxCount
=
useMemo
(()
=>
calculateYearMaxCount
(
yearData
),
[
yearData
]);
const
currentYear
=
useMemo
(()
=>
new
Date
().
getFullYear
(),
[]);
const
isCurrentYear
=
selectedYear
===
currentYear
;
const
handlePrevYear
=
()
=>
{
if
(
selectedYear
>
MIN_YEAR
)
{
setSelectedYear
(
selectedYear
-
1
);
}
};
const
handleNextYear
=
()
=>
{
if
(
selectedYear
<
MAX_YEAR
)
{
setSelectedYear
(
selectedYear
+
1
);
}
};
const
handleToday
=
()
=>
{
setSelectedYear
(
currentYear
);
};
const
canGoPrev
=
selectedYear
>
MIN_YEAR
;
const
canGoNext
=
selectedYear
<
MAX_YEAR
;
return
(
<
section
className=
"relative w-full min-h-full flex flex-col justify-start items-center bg-background"
>
<
MobileHeader
/>
<
div
className=
"relative w-full flex flex-col items-center px-3 sm:px-4 md:px-6 lg:px-8 pb-8"
>
<
div
className=
"w-full max-w-7xl flex flex-col gap-3 sm:gap-4 py-3 sm:py-4"
>
<
div
className=
"flex items-center justify-between pb-2"
>
<
h1
className=
"text-2xl sm:text-3xl md:text-4xl font-bold text-foreground tracking-tight leading-none"
>
{
selectedYear
}
</
h1
>
<
div
className=
"inline-flex items-center gap-2 shrink-0"
>
<
Button
variant=
"ghost"
size=
"icon"
onClick=
{
handlePrevYear
}
disabled=
{
!
canGoPrev
}
aria
-
label=
"Previous year"
className=
"rounded-full hover:bg-accent/50 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
<
ChevronLeftIcon
/>
</
Button
>
<
Button
variant=
{
isCurrentYear
?
"secondary"
:
"ghost"
}
onClick=
{
handleToday
}
disabled=
{
isCurrentYear
}
aria
-
label=
{
t
(
"common.today"
)
}
className=
{
cn
(
"h-9 px-4 rounded-full font-medium text-sm transition-colors"
,
isCurrentYear
?
"bg-accent text-accent-foreground cursor-default"
:
"hover:bg-accent/50 text-muted-foreground hover:text-foreground"
,
)
}
>
{
t
(
"common.today"
)
}
</
Button
>
<
Button
variant=
"ghost"
size=
"icon"
onClick=
{
handleNextYear
}
disabled=
{
!
canGoNext
}
aria
-
label=
"Next year"
className=
"rounded-full hover:bg-accent/50 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
<
ChevronRightIcon
/>
</
Button
>
</
div
>
</
div
>
{
loading
?
(
<
div
className=
"w-full flex items-center justify-center py-12"
>
<
div
className=
"flex flex-col items-center gap-3"
>
<
div
className=
"h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"
/>
<
p
className=
"text-sm text-muted-foreground"
>
Loading calendar...
</
p
>
</
div
>
</
div
>
)
:
(
<
TooltipProvider
>
<
div
className=
"w-full animate-fade-in"
>
<
div
className=
"grid gap-2 sm:gap-2.5 md:gap-3 lg:gap-3 grid-cols-2 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4"
>
{
months
.
map
((
month
)
=>
(
<
div
key=
{
month
}
className=
"flex flex-col gap-1 sm:gap-1.5 rounded-lg border bg-card p-1.5 sm:p-2 md:p-2.5 shadow-sm hover:shadow-md hover:border-border/60 transition-all duration-200"
>
<
div
className=
"text-xs font-semibold text-foreground text-center tracking-tight"
>
{
getMonthLabel
(
month
)
}
</
div
>
<
CompactMonthCalendar
month=
{
month
}
data=
{
yearData
}
maxCount=
{
yearMaxCount
}
size=
"small"
onClick=
{
navigateToDateFilter
}
/>
</
div
>
))
}
</
div
>
</
div
>
</
TooltipProvider
>
)
}
</
div
>
</
div
>
</
section
>
);
};
export
default
Calendar
;
web/src/router/index.tsx
View file @
ea3371ba
...
@@ -9,7 +9,6 @@ import Home from "@/pages/Home";
...
@@ -9,7 +9,6 @@ import Home from "@/pages/Home";
const
AdminSignIn
=
lazy
(()
=>
import
(
"@/pages/AdminSignIn"
));
const
AdminSignIn
=
lazy
(()
=>
import
(
"@/pages/AdminSignIn"
));
const
Archived
=
lazy
(()
=>
import
(
"@/pages/Archived"
));
const
Archived
=
lazy
(()
=>
import
(
"@/pages/Archived"
));
const
AuthCallback
=
lazy
(()
=>
import
(
"@/pages/AuthCallback"
));
const
AuthCallback
=
lazy
(()
=>
import
(
"@/pages/AuthCallback"
));
const
Calendar
=
lazy
(()
=>
import
(
"@/pages/Calendar"
));
const
Explore
=
lazy
(()
=>
import
(
"@/pages/Explore"
));
const
Explore
=
lazy
(()
=>
import
(
"@/pages/Explore"
));
const
Inboxes
=
lazy
(()
=>
import
(
"@/pages/Inboxes"
));
const
Inboxes
=
lazy
(()
=>
import
(
"@/pages/Inboxes"
));
const
MemoDetail
=
lazy
(()
=>
import
(
"@/pages/MemoDetail"
));
const
MemoDetail
=
lazy
(()
=>
import
(
"@/pages/MemoDetail"
));
...
@@ -115,14 +114,6 @@ const router = createBrowserRouter([
...
@@ -115,14 +114,6 @@ const router = createBrowserRouter([
</
Suspense
>
</
Suspense
>
),
),
},
},
{
path
:
Routes
.
CALENDAR
,
element
:
(
<
Suspense
fallback=
{
<
Skeleton
type=
"route"
showEditor=
{
false
}
/>
}
>
<
Calendar
/>
</
Suspense
>
),
},
{
{
path
:
Routes
.
INBOX
,
path
:
Routes
.
INBOX
,
element
:
(
element
:
(
...
...
web/src/router/routes.ts
View file @
ea3371ba
export
const
ROUTES
=
{
export
const
ROUTES
=
{
ROOT
:
"/"
,
ROOT
:
"/"
,
ATTACHMENTS
:
"/attachments"
,
ATTACHMENTS
:
"/attachments"
,
CALENDAR
:
"/calendar"
,
INBOX
:
"/inbox"
,
INBOX
:
"/inbox"
,
ARCHIVED
:
"/archived"
,
ARCHIVED
:
"/archived"
,
SETTING
:
"/setting"
,
SETTING
:
"/setting"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment