@@ -3,7 +3,116 @@ import { api, type TimeEntryResponse, type TimeEntry } from '@/packages/api/src'
33import { getCurrentMembershipId , getCurrentOrganizationId } from '@/utils/useUser' ;
44import { computed , type Ref } from 'vue' ;
55import { getDayJsInstance } from '@/packages/ui/src/utils/time' ;
6- import { getUserTimezone } from '@/packages/ui/src/utils/settings' ;
6+ import { getUserTimezone , getWeekStart } from '@/packages/ui/src/utils/settings' ;
7+
8+ const weekStartMap : Record < string , number > = {
9+ sunday : 0 ,
10+ monday : 1 ,
11+ tuesday : 2 ,
12+ wednesday : 3 ,
13+ thursday : 4 ,
14+ friday : 5 ,
15+ saturday : 6 ,
16+ } ;
17+
18+ /**
19+ * Calculate expanded date range to include previous and next periods with timezone transformations.
20+ * This allows smooth navigation between calendar views without loading delays.
21+ */
22+ export function getExpandedCalendarDateRange (
23+ calendarStart : Date ,
24+ calendarEnd : Date
25+ ) : { start : string ; end : string } {
26+ const dayjs = getDayJsInstance ( ) ;
27+ const duration = dayjs ( calendarEnd ) . diff ( dayjs ( calendarStart ) , 'milliseconds' ) ;
28+
29+ // Calculate previous period
30+ const previousStart = dayjs ( calendarStart ) . subtract ( duration , 'milliseconds' ) ;
31+ // Calculate next period
32+ const nextEnd = dayjs ( calendarEnd ) . add ( duration , 'milliseconds' ) ;
33+
34+ // Apply timezone transformations
35+ const timezone = getUserTimezone ( ) ;
36+ const formattedStart = previousStart . utc ( ) . tz ( timezone , true ) . utc ( ) . format ( ) ;
37+ const formattedEnd = nextEnd . utc ( ) . tz ( timezone , true ) . utc ( ) . format ( ) ;
38+
39+ return {
40+ start : formattedStart ,
41+ end : formattedEnd ,
42+ } ;
43+ }
44+
45+ /**
46+ * Get the initial week view date range based on user's week start preference.
47+ * Matches FullCalendar's timeGridWeek initial view.
48+ */
49+ export function getInitialWeekRange ( ) : { start : Date ; end : Date } {
50+ const dayjs = getDayJsInstance ( ) ;
51+ const weekStart = getWeekStart ( ) ;
52+ const firstDay = weekStartMap [ weekStart ] ?? 1 ;
53+
54+ const now = dayjs ( ) ;
55+ const currentDayOfWeek = now . day ( ) ;
56+ const daysFromWeekStart = ( currentDayOfWeek - firstDay + 7 ) % 7 ;
57+ const calendarStart = now . subtract ( daysFromWeekStart , 'day' ) . startOf ( 'day' ) ;
58+ const calendarEnd = calendarStart . add ( 7 , 'day' ) ;
59+
60+ return {
61+ start : calendarStart . toDate ( ) ,
62+ end : calendarEnd . toDate ( ) ,
63+ } ;
64+ }
65+
66+ /**
67+ * Create the query key for calendar time entries.
68+ */
69+ export function createCalendarQueryKey (
70+ start : string | null ,
71+ end : string | null ,
72+ organizationId : string | null
73+ ) : readonly [
74+ 'timeEntries' ,
75+ 'calendar' ,
76+ { start : string | null ; end : string | null ; organization : string | null } ,
77+ ] {
78+ return [ 'timeEntries' , 'calendar' , { start, end, organization : organizationId } ] as const ;
79+ }
80+
81+ /**
82+ * Fetch all calendar entries with pagination.
83+ */
84+ export async function fetchAllCalendarEntries (
85+ organizationId : string ,
86+ memberId : string | undefined ,
87+ start : string ,
88+ end : string
89+ ) : Promise < TimeEntryResponse > {
90+ const allEntries : TimeEntry [ ] = [ ] ;
91+
92+ while ( true ) {
93+ const response = await api . getTimeEntries ( {
94+ params : {
95+ organization : organizationId ,
96+ } ,
97+ queries : {
98+ start,
99+ end,
100+ member_id : memberId ,
101+ offset : allEntries . length || undefined ,
102+ } ,
103+ } ) ;
104+
105+ if ( response . data . length === 0 ) {
106+ return { data : allEntries , meta : response . meta } ;
107+ }
108+
109+ allEntries . push ( ...response . data ) ;
110+
111+ if ( allEntries . length >= response . meta . total ) {
112+ return { data : allEntries , meta : response . meta } ;
113+ }
114+ }
115+ }
7116
8117export function useTimeEntriesCalendarQuery (
9118 calendarStart : Ref < Date | undefined > ,
@@ -13,68 +122,30 @@ export function useTimeEntriesCalendarQuery(
13122 return ! ! getCurrentOrganizationId ( ) && ! ! calendarStart . value && ! ! calendarEnd . value ;
14123 } ) ;
15124
16- // Calculate expanded date range to include previous and next periods with timezone transformations
17125 const expandedDateRange = computed ( ( ) => {
18126 if ( ! calendarStart . value || ! calendarEnd . value ) {
19127 return { start : null , end : null } ;
20128 }
21-
22- const dayjs = getDayJsInstance ( ) ;
23- const duration = dayjs ( calendarEnd . value ) . diff ( dayjs ( calendarStart . value ) , 'milliseconds' ) ;
24-
25- // Calculate previous period
26- const previousStart = dayjs ( calendarStart . value ) . subtract ( duration , 'milliseconds' ) ;
27- // Calculate next period
28- const nextEnd = dayjs ( calendarEnd . value ) . add ( duration , 'milliseconds' ) ;
29-
30- // Apply timezone transformations
31- const formattedStart = previousStart . utc ( ) . tz ( getUserTimezone ( ) , true ) . utc ( ) . format ( ) ;
32- const formattedEnd = nextEnd . utc ( ) . tz ( getUserTimezone ( ) , true ) . utc ( ) . format ( ) ;
33-
34- return {
35- start : formattedStart ,
36- end : formattedEnd ,
37- } ;
129+ return getExpandedCalendarDateRange ( calendarStart . value , calendarEnd . value ) ;
38130 } ) ;
39131
40132 return useQuery < TimeEntryResponse > ( {
41- queryKey : computed ( ( ) => [
42- 'timeEntries' ,
43- 'calendar' ,
44- {
45- start : expandedDateRange . value . start ,
46- end : expandedDateRange . value . end ,
47- organization : getCurrentOrganizationId ( ) ,
48- } ,
49- ] ) ,
133+ queryKey : computed ( ( ) =>
134+ createCalendarQueryKey (
135+ expandedDateRange . value . start ,
136+ expandedDateRange . value . end ,
137+ getCurrentOrganizationId ( )
138+ )
139+ ) ,
50140 enabled : enableCalendarQuery ,
51141 placeholderData : ( previousData ) => previousData ,
52142 queryFn : async ( ) => {
53- const allEntries : TimeEntry [ ] = [ ] ;
54-
55- while ( true ) {
56- const response = await api . getTimeEntries ( {
57- params : {
58- organization : getCurrentOrganizationId ( ) || '' ,
59- } ,
60- queries : {
61- start : expandedDateRange . value . start ! ,
62- end : expandedDateRange . value . end ! ,
63- member_id : getCurrentMembershipId ( ) ,
64- offset : allEntries . length || undefined ,
65- } ,
66- } ) ;
67-
68- if ( response . data . length === 0 ) {
69- return { data : allEntries , meta : response . meta } ;
70- }
71-
72- allEntries . push ( ...response . data ) ;
73-
74- if ( allEntries . length >= response . meta . total ) {
75- return { data : allEntries , meta : response . meta } ;
76- }
77- }
143+ return fetchAllCalendarEntries (
144+ getCurrentOrganizationId ( ) || '' ,
145+ getCurrentMembershipId ( ) ,
146+ expandedDateRange . value . start ! ,
147+ expandedDateRange . value . end !
148+ ) ;
78149 } ,
79150 staleTime : 1000 * 30 , // 30 seconds
80151 } ) ;
0 commit comments