* draft for tablet view * adapt sidebar to tablet view * split menubar to handle tablet and desktop separatly * split sidebar to handle tablet and desktop separatly * prettier day navigationin tablet view * added missing translations * coderabbit comments * fix UI of view switcher on tablet, remove hard text * fix style of selected view option * fix wrong I18n key * extracted menubar recurring components to own files * adjust test case of calendar component * refactor function change calendar view Co-authored-by: Camille Moussu <cmoussu@linagora.com> Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import CalendarApp from '@/components/Calendar/Calendar'
|
||||
import CalendarLayout from '@/components/Calendar/CalendarLayout'
|
||||
import { CALENDAR_VIEWS } from '@/components/Calendar/utils/constants'
|
||||
import * as calendarDetailThunks from '@/features/Calendars/services'
|
||||
import * as servicesModule from '@/features/Calendars/services'
|
||||
import { searchUsers } from '@/features/User/userAPI'
|
||||
@@ -14,7 +15,16 @@ const mockedSearchUsers = searchUsers as jest.MockedFunction<typeof searchUsers>
|
||||
// Test wrapper component to provide calendarRef
|
||||
function CalendarTestWrapper() {
|
||||
const calendarRef = useRef(null)
|
||||
return <CalendarApp calendarRef={calendarRef} />
|
||||
return (
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={calendarRef}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
describe('CalendarSelection', () => {
|
||||
@@ -143,7 +153,14 @@ describe('CalendarSelection', () => {
|
||||
const mockCalendarRef = { current: null }
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
})
|
||||
@@ -159,7 +176,14 @@ describe('CalendarSelection', () => {
|
||||
const mockCalendarRef = { current: null }
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
})
|
||||
@@ -194,7 +218,14 @@ describe('CalendarSelection', () => {
|
||||
)
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
})
|
||||
@@ -207,7 +238,14 @@ describe('CalendarSelection', () => {
|
||||
const mockCalendarRef = { current: null }
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
})
|
||||
@@ -423,8 +461,9 @@ describe('calendar Availability search', () => {
|
||||
})
|
||||
|
||||
const calendarApi = calendarRef.current
|
||||
expect(calendarApi).not.toBeNull()
|
||||
await act(async () => {
|
||||
calendarApi.changeView('dayGridMonth')
|
||||
calendarApi?.changeView(CALENDAR_VIEWS.dayGridMonth)
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByRole('columnheader').length).toBe(14)
|
||||
@@ -472,9 +511,10 @@ describe('calendar Availability search', () => {
|
||||
|
||||
const calendarRef = window.__calendarRef
|
||||
const calendarApi = calendarRef.current
|
||||
expect(calendarApi).not.toBeNull()
|
||||
|
||||
await act(async () => {
|
||||
calendarApi.changeView('dayGridMonth')
|
||||
calendarApi?.changeView(CALENDAR_VIEWS.dayGridMonth)
|
||||
fireEvent.click(screen.getByTestId('ChevronRightIcon'))
|
||||
})
|
||||
|
||||
@@ -554,7 +594,14 @@ describe('calendar Availability search', () => {
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={{ current: null }} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={{ current: null }}
|
||||
/>,
|
||||
stateWithManyCalendars
|
||||
)
|
||||
})
|
||||
@@ -612,7 +659,14 @@ describe('calendar Availability search', () => {
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={{ current: null }} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={{ current: null }}
|
||||
/>,
|
||||
calendarsWithSelected
|
||||
)
|
||||
})
|
||||
@@ -646,10 +700,20 @@ describe('calendar Availability search', () => {
|
||||
)
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(<CalendarApp calendarRef={{ current: null }} />, {
|
||||
...preloadedState,
|
||||
calendars: { ...preloadedState.calendars, pending: false }
|
||||
})
|
||||
renderWithProviders(
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={{ current: null }}
|
||||
/>,
|
||||
{
|
||||
...preloadedState,
|
||||
calendars: { ...preloadedState.calendars, pending: false }
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await waitFor(
|
||||
@@ -682,7 +746,14 @@ describe('calendar Availability search', () => {
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={{ current: null }} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={{ current: null }}
|
||||
/>,
|
||||
stateWithUndefinedCalendars
|
||||
)
|
||||
})
|
||||
@@ -701,7 +772,14 @@ describe('calendar Availability search', () => {
|
||||
|
||||
await act(async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={{ current: null }} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={{ current: null }}
|
||||
/>,
|
||||
stateWithUndefinedTemp
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CalendarLayout from '@/components/Calendar/CalendarLayout'
|
||||
import { CALENDAR_VIEWS } from '@/components/Calendar/utils/constants'
|
||||
import { act, waitFor } from '@testing-library/react'
|
||||
import { renderWithProviders } from '../utils/Renderwithproviders'
|
||||
|
||||
@@ -51,7 +52,7 @@ describe('Calendar – dayHeaderContent respects selected timezone', () => {
|
||||
await waitFor(() => expect(calendarRef?.current).not.toBeNull())
|
||||
|
||||
await act(async () => {
|
||||
calendarRef.current?.changeView('timeGridWeek')
|
||||
calendarRef.current?.changeView(CALENDAR_VIEWS.timeGridWeek)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -75,7 +75,14 @@ describe('CalendarApp integration', () => {
|
||||
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
}
|
||||
@@ -172,7 +179,14 @@ describe('CalendarApp integration', () => {
|
||||
})
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
const card = screen.getByTestId('event-card-event1')
|
||||
@@ -191,7 +205,14 @@ describe('CalendarApp integration', () => {
|
||||
})
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
|
||||
@@ -211,7 +232,14 @@ describe('CalendarApp integration', () => {
|
||||
})
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
|
||||
@@ -222,46 +250,56 @@ describe('CalendarApp integration', () => {
|
||||
|
||||
it('does render a title for events without any attendees or user as organizer', async () => {
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(<CalendarApp calendarRef={mockCalendarRef} />, {
|
||||
user: {
|
||||
userData: {
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
tokens: {
|
||||
accessToken: 'token'
|
||||
}
|
||||
},
|
||||
calendars: {
|
||||
list: {
|
||||
'667037022b752d0026472254/cal1': {
|
||||
name: 'Calendar 1',
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
color: { light: '#FF0000', dark: '#000' },
|
||||
owner: { emails: ['alice@example.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
id: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
uid: 'event1',
|
||||
start: new Date().toISOString(),
|
||||
end: new Date(Date.now() + 3600000).toISOString(),
|
||||
partstat: 'ACCEPTED',
|
||||
organizer: {
|
||||
cn: 'Alice',
|
||||
cal_address: 'alice@example.com'
|
||||
},
|
||||
class: 'PUBLIC',
|
||||
title: 'Public Event'
|
||||
}
|
||||
}
|
||||
renderWithProviders(
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
{
|
||||
user: {
|
||||
userData: {
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
sid: 'mockSid',
|
||||
openpaasId: '667037022b752d0026472254'
|
||||
},
|
||||
tokens: {
|
||||
accessToken: 'token'
|
||||
}
|
||||
},
|
||||
pending: false
|
||||
calendars: {
|
||||
list: {
|
||||
'667037022b752d0026472254/cal1': {
|
||||
name: 'Calendar 1',
|
||||
id: '667037022b752d0026472254/cal1',
|
||||
color: { light: '#FF0000', dark: '#000' },
|
||||
owner: { emails: ['alice@example.com'] },
|
||||
events: {
|
||||
event1: {
|
||||
id: 'event1',
|
||||
calId: '667037022b752d0026472254/cal1',
|
||||
uid: 'event1',
|
||||
start: new Date().toISOString(),
|
||||
end: new Date(Date.now() + 3600000).toISOString(),
|
||||
partstat: 'ACCEPTED',
|
||||
organizer: {
|
||||
cn: 'Alice',
|
||||
cal_address: 'alice@example.com'
|
||||
},
|
||||
class: 'PUBLIC',
|
||||
title: 'Public Event'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pending: false
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
expect(screen.getByText('Public Event')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CALENDAR_VIEWS } from '@/components/Calendar/utils/constants'
|
||||
import { Menubar } from '@/components/Menubar/Menubar'
|
||||
import * as oidcAuth from '@/features/User/oidcAuth'
|
||||
import { redirectTo } from '@/utils/navigation'
|
||||
@@ -35,8 +36,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -53,8 +55,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -71,8 +74,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -100,8 +104,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -131,8 +136,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -161,8 +167,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -191,8 +198,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -221,8 +229,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -251,8 +260,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -283,8 +293,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -315,8 +326,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -335,8 +347,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -356,8 +369,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -377,8 +391,9 @@ describe('Calendar App Component Display Tests', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -427,8 +442,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -447,8 +463,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -467,8 +484,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -487,8 +505,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -509,8 +528,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -531,8 +551,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -553,8 +574,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -579,8 +601,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -618,8 +641,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -650,8 +674,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedStateWithSettings
|
||||
)
|
||||
@@ -681,8 +706,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -707,8 +733,9 @@ describe('Menubar interaction with expanded Dialog', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -745,8 +772,9 @@ describe('Menubar logout flow', () => {
|
||||
<Menubar
|
||||
calendarRef={{ current: null }}
|
||||
onRefresh={() => {}}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={new Date()}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -815,8 +843,9 @@ describe('Logo click navigation to current week', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
onViewChange={mockOnViewChange}
|
||||
onDateChange={mockOnDateChange}
|
||||
/>,
|
||||
@@ -832,9 +861,9 @@ describe('Logo click navigation to current week', () => {
|
||||
expect(store.getState().settings.view).toBe('calendar')
|
||||
})
|
||||
|
||||
expect(mockChangeView).toHaveBeenCalledWith('timeGridWeek')
|
||||
expect(mockChangeView).toHaveBeenCalledWith(CALENDAR_VIEWS.timeGridWeek)
|
||||
expect(mockToday).toHaveBeenCalled()
|
||||
expect(mockOnViewChange).toHaveBeenCalledWith('timeGridWeek')
|
||||
expect(mockOnViewChange).toHaveBeenCalledWith(CALENDAR_VIEWS.timeGridWeek)
|
||||
expect(mockOnDateChange).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -857,8 +886,9 @@ describe('Logo click navigation to current week', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="timeGridDay"
|
||||
currentView={CALENDAR_VIEWS.timeGridDay}
|
||||
onViewChange={mockOnViewChange}
|
||||
onDateChange={mockOnDateChange}
|
||||
/>,
|
||||
@@ -874,9 +904,9 @@ describe('Logo click navigation to current week', () => {
|
||||
expect(store.getState().settings.view).toBe('calendar')
|
||||
})
|
||||
|
||||
expect(mockChangeView).toHaveBeenCalledWith('timeGridWeek')
|
||||
expect(mockChangeView).toHaveBeenCalledWith(CALENDAR_VIEWS.timeGridWeek)
|
||||
expect(mockToday).toHaveBeenCalled()
|
||||
expect(mockOnViewChange).toHaveBeenCalledWith('timeGridWeek')
|
||||
expect(mockOnViewChange).toHaveBeenCalledWith(CALENDAR_VIEWS.timeGridWeek)
|
||||
expect(mockOnDateChange).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -899,8 +929,9 @@ describe('Logo click navigation to current week', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="timeGridWeek"
|
||||
currentView={CALENDAR_VIEWS.timeGridWeek}
|
||||
onViewChange={mockOnViewChange}
|
||||
onDateChange={mockOnDateChange}
|
||||
/>,
|
||||
@@ -932,8 +963,9 @@ describe('Logo click navigation to current week', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
onViewChange={mockOnViewChange}
|
||||
onDateChange={mockOnDateChange}
|
||||
/>,
|
||||
@@ -970,8 +1002,9 @@ describe('Logo click navigation to current week', () => {
|
||||
<Menubar
|
||||
calendarRef={mockCalendarRef}
|
||||
onRefresh={mockOnRefresh}
|
||||
onToggleSidebar={() => {}}
|
||||
currentDate={mockCurrentDate}
|
||||
currentView="dayGridMonth"
|
||||
currentView={CALENDAR_VIEWS.dayGridMonth}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
@@ -985,7 +1018,7 @@ describe('Logo click navigation to current week', () => {
|
||||
expect(store.getState().settings.view).toBe('calendar')
|
||||
})
|
||||
|
||||
expect(mockChangeView).toHaveBeenCalledWith('timeGridWeek')
|
||||
expect(mockChangeView).toHaveBeenCalledWith(CALENDAR_VIEWS.timeGridWeek)
|
||||
expect(mockToday).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -49,7 +49,14 @@ describe('MiniCalendar', () => {
|
||||
}
|
||||
const mockCalendarRef = { current: null }
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
preloadedState
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,14 @@ describe('Calendar - Timezone Integration', () => {
|
||||
|
||||
it('renders TimezoneSelector in week view', async () => {
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
baseState
|
||||
)
|
||||
|
||||
@@ -57,7 +64,14 @@ describe('Calendar - Timezone Integration', () => {
|
||||
const setTimeZoneSpy = jest.spyOn(SettingsSlice, 'setTimeZone')
|
||||
|
||||
renderWithProviders(
|
||||
<CalendarApp calendarRef={mockCalendarRef} />,
|
||||
<CalendarApp
|
||||
setCurrentView={jest.fn()}
|
||||
onViewChange={jest.fn()}
|
||||
openSidebar={false}
|
||||
onCloseSidebar={jest.fn()}
|
||||
currentView="timeGridWeek"
|
||||
calendarRef={mockCalendarRef}
|
||||
/>,
|
||||
baseState
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ const isValidLanguage = (
|
||||
return !!lang && SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage)
|
||||
}
|
||||
|
||||
function App() {
|
||||
function App(): JSX.Element {
|
||||
const error = useAppSelector(state => state.user.error)
|
||||
const appLoading = useAppSelector(state => state.loading.isLoading)
|
||||
const userLanguage = useAppSelector(state => state.user.coreConfig.language)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CalendarEvent } from '@/features/Events/EventsTypes'
|
||||
import ImportAlert from '@/features/Events/ImportAlert'
|
||||
import SearchResultsPage from '@/features/Search/SearchResultsPage'
|
||||
import { setTimeZone } from '@/features/Settings/SettingsSlice'
|
||||
import { useScreenSizeDetection } from '@/useScreenSizeDetection'
|
||||
import { setDisplayedDateAndRange } from '@/utils/CalendarRangeManager'
|
||||
import { extractEventBaseUuid } from '@/utils/extractEventBaseUuid'
|
||||
import { setSelectedCalendars as setSelectedCalendarsToStorage } from '@/utils/storage/setSelectedCalendars'
|
||||
@@ -20,7 +21,7 @@ import interactionPlugin from '@fullcalendar/interaction'
|
||||
import momentTimezonePlugin from '@fullcalendar/moment-timezone'
|
||||
import FullCalendar from '@fullcalendar/react'
|
||||
import timeGridPlugin from '@fullcalendar/timegrid'
|
||||
import { Box, Button, radius } from '@linagora/twake-mui'
|
||||
import { Fab } from '@linagora/twake-mui'
|
||||
import AddIcon from '@mui/icons-material/Add'
|
||||
import moment from 'moment-timezone'
|
||||
import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
|
||||
@@ -32,18 +33,17 @@ import { EventErrorHandler } from '../Error/EventErrorHandler'
|
||||
import { EditModeDialog } from '../Event/EditModeDialog'
|
||||
import { Menubar, MenubarProps } from '../Menubar/Menubar'
|
||||
import './Calendar.styl'
|
||||
import CalendarSelection from './CalendarSelection'
|
||||
import './CustomCalendar.styl'
|
||||
import { useCalendarEventHandlers } from './hooks/useCalendarEventHandlers'
|
||||
import { useCalendarViewHandlers } from './hooks/useCalendarViewHandlers'
|
||||
import { MiniCalendar } from './MiniCalendar'
|
||||
import { TempCalendarsInput } from './TempCalendarsInput'
|
||||
import Sidebar from './Sidebar/SideBar'
|
||||
import { TimezoneSelector } from './TimezoneSelector'
|
||||
import {
|
||||
eventToFullCalendarFormat,
|
||||
extractEvents,
|
||||
updateSlotLabelVisibility
|
||||
} from './utils/calendarUtils'
|
||||
import { CALENDAR_VIEWS } from './utils/constants'
|
||||
|
||||
const localeMap: Record<string, LocaleInput | undefined> = {
|
||||
fr: frLocale,
|
||||
@@ -55,16 +55,24 @@ const localeMap: Record<string, LocaleInput | undefined> = {
|
||||
interface CalendarAppProps {
|
||||
calendarRef: MutableRefObject<CalendarApi | null>
|
||||
onDateChange?: (date: Date) => void
|
||||
onViewChange?: (view: string) => void
|
||||
onViewChange: (view: string) => void
|
||||
menubarProps?: MenubarProps
|
||||
openSidebar: boolean
|
||||
onCloseSidebar: () => void
|
||||
setCurrentView: (view: string) => void
|
||||
currentView: string
|
||||
}
|
||||
|
||||
export default function CalendarApp({
|
||||
calendarRef,
|
||||
onDateChange,
|
||||
onViewChange,
|
||||
menubarProps
|
||||
}: CalendarAppProps) {
|
||||
menubarProps,
|
||||
openSidebar,
|
||||
onCloseSidebar,
|
||||
setCurrentView,
|
||||
currentView
|
||||
}: CalendarAppProps): JSX.Element {
|
||||
const [selectedDate, setSelectedDate] = useState(new Date())
|
||||
const [debouncedDate, setDebouncedDate] = useState(new Date())
|
||||
useEffect(() => {
|
||||
@@ -84,6 +92,9 @@ export default function CalendarApp({
|
||||
const hideDeclinedEvents = useAppSelector(
|
||||
state => state.settings.hideDeclinedEvents
|
||||
)
|
||||
|
||||
const { isTablet } = useScreenSizeDetection()
|
||||
|
||||
const hiddenDays = useMemo(() => {
|
||||
if (!hideWorkingDays || !workingDays || workingDays.length === 0) return []
|
||||
const validWorkingDays = workingDays.filter(d => d >= 0 && d <= 6)
|
||||
@@ -113,7 +124,6 @@ export default function CalendarApp({
|
||||
[calendarIdsString]
|
||||
)
|
||||
|
||||
const [currentView, setCurrentView] = useState('timeGridWeek')
|
||||
const timezone =
|
||||
useAppSelector(state => state.settings.timeZone) ?? browserDefaultTimeZone
|
||||
|
||||
@@ -297,6 +307,20 @@ export default function CalendarApp({
|
||||
const [tempUsers, setTempUsers] = useState<User[]>([])
|
||||
const [tempEvent, setTempEvent] = useState<CalendarEvent>({} as CalendarEvent)
|
||||
|
||||
useEffect(() => {
|
||||
if (view !== 'calendar') return
|
||||
const targetView =
|
||||
currentView ||
|
||||
(isTablet ? CALENDAR_VIEWS.timeGridDay : CALENDAR_VIEWS.timeGridWeek)
|
||||
|
||||
if (calendarRef.current?.view.type === targetView) return
|
||||
const id = requestAnimationFrame(() => {
|
||||
if (calendarRef.current?.view.type !== targetView) {
|
||||
calendarRef.current?.changeView(targetView)
|
||||
}
|
||||
})
|
||||
return () => cancelAnimationFrame(id)
|
||||
}, [view, isTablet, currentView, calendarRef])
|
||||
// Event handlers
|
||||
const eventHandlers = useCalendarEventHandlers({
|
||||
setSelectedRange,
|
||||
@@ -321,7 +345,7 @@ export default function CalendarApp({
|
||||
calendarRef,
|
||||
setSelectedDate,
|
||||
setSelectedMiniDate,
|
||||
onViewChange,
|
||||
onViewChange: setCurrentView,
|
||||
calendars,
|
||||
tempcalendars,
|
||||
// Note: To preserve current logic, this will temporarily disable eslint for react-hooks/refs
|
||||
@@ -341,68 +365,40 @@ export default function CalendarApp({
|
||||
<main
|
||||
className={`main-layout calendar-layout ${menubarProps?.isIframe ? ' isInIframe' : ''}`}
|
||||
>
|
||||
<Box
|
||||
className="sidebar"
|
||||
sx={{
|
||||
paddingTop: 0,
|
||||
paddingBottom: 3,
|
||||
paddingLeft: 3,
|
||||
paddingRight: 2,
|
||||
width: '270px'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 10,
|
||||
backgroundColor: '#fff',
|
||||
paddingTop: menubarProps?.isIframe ? '10px' : 3
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="medium"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={() =>
|
||||
eventHandlers.handleDateSelect(null as unknown as DateSelectArg)
|
||||
}
|
||||
sx={{
|
||||
borderRadius: radius.lg,
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 'normal'
|
||||
}}
|
||||
>
|
||||
<AddIcon sx={{ marginRight: 0.5, fontSize: '20px' }} />{' '}
|
||||
{t('event.createEvent')}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<MiniCalendar
|
||||
calendarRef={calendarRef}
|
||||
selectedDate={selectedMiniDate}
|
||||
setSelectedMiniDate={setSelectedMiniDate}
|
||||
/>
|
||||
<Box sx={{ mb: 3, mt: 2 }}>
|
||||
<TempCalendarsInput
|
||||
tempUsers={tempUsers}
|
||||
setTempUsers={setTempUsers}
|
||||
handleToggleEventPreview={() => {
|
||||
eventHandlers.handleDateSelect(null as unknown as DateSelectArg)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<div className="calendarList">
|
||||
<CalendarSelection
|
||||
selectedCalendars={selectedCalendars}
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
<Sidebar
|
||||
open={openSidebar}
|
||||
onClose={onCloseSidebar}
|
||||
calendarRef={calendarRef}
|
||||
isIframe={menubarProps?.isIframe}
|
||||
onCreateEvent={() => eventHandlers.handleDateSelect(null)}
|
||||
onViewChange={onViewChange}
|
||||
selectedMiniDate={selectedMiniDate}
|
||||
setSelectedMiniDate={setSelectedMiniDate}
|
||||
selectedCalendars={selectedCalendars}
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
tempUsers={tempUsers}
|
||||
setTempUsers={setTempUsers}
|
||||
currentView={currentView}
|
||||
/>
|
||||
<div className="calendar">
|
||||
<ImportAlert />
|
||||
{menubarProps?.isIframe && <Menubar {...menubarProps} />}
|
||||
{isTablet && (
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label={t('event.createEvent')}
|
||||
onClick={() => eventHandlers.handleDateSelect(null)}
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 24,
|
||||
right: 24,
|
||||
zIndex: 20,
|
||||
borderRadius: '16px'
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
)}
|
||||
{view === 'calendar' && (
|
||||
<FullCalendar
|
||||
key={hiddenDays.join(',')}
|
||||
@@ -417,7 +413,12 @@ export default function CalendarApp({
|
||||
interactionPlugin,
|
||||
momentTimezonePlugin
|
||||
]}
|
||||
initialView="timeGridWeek"
|
||||
initialView={
|
||||
currentView ||
|
||||
(isTablet
|
||||
? CALENDAR_VIEWS.timeGridDay
|
||||
: CALENDAR_VIEWS.timeGridWeek)
|
||||
}
|
||||
firstDay={1}
|
||||
editable={true}
|
||||
locale={localeMap[lang]}
|
||||
@@ -448,7 +449,8 @@ export default function CalendarApp({
|
||||
a.extendedProps.priority - b.extendedProps.priority
|
||||
}
|
||||
weekNumbers={
|
||||
currentView === 'timeGridWeek' || currentView === 'timeGridDay'
|
||||
currentView === CALENDAR_VIEWS.timeGridWeek ||
|
||||
currentView === CALENDAR_VIEWS.timeGridDay
|
||||
}
|
||||
weekNumberFormat={{ week: 'long' }}
|
||||
weekNumberContent={arg => {
|
||||
@@ -474,7 +476,7 @@ export default function CalendarApp({
|
||||
month: 'short',
|
||||
timeZone: timezone
|
||||
})
|
||||
if (arg.view.type === 'dayGridMonth') {
|
||||
if (arg.view.type === CALENDAR_VIEWS.dayGridMonth) {
|
||||
return (
|
||||
<span
|
||||
className={`fc-daygrid-day-number ${
|
||||
@@ -502,7 +504,7 @@ export default function CalendarApp({
|
||||
calendarRef.current?.getDate() || new Date(arg.start)
|
||||
setDisplayedDateAndRange(calendarCurrentDate)
|
||||
|
||||
if (arg.view.type === 'dayGridMonth') {
|
||||
if (arg.view.type === CALENDAR_VIEWS.dayGridMonth) {
|
||||
const start = new Date(arg.start).getTime()
|
||||
const end = new Date(arg.end).getTime()
|
||||
const middle = start + (end - start) / 2
|
||||
@@ -519,9 +521,7 @@ export default function CalendarApp({
|
||||
}
|
||||
|
||||
// Notify parent about view change
|
||||
if (onViewChange) {
|
||||
onViewChange(arg.view.type)
|
||||
}
|
||||
setCurrentView(arg.view.type)
|
||||
|
||||
// Update slot label visibility when view changes
|
||||
setTimeout(() => {
|
||||
@@ -543,7 +543,7 @@ export default function CalendarApp({
|
||||
return (
|
||||
<div className="fc-daygrid-day-top">
|
||||
<small>{weekDay}</small>
|
||||
{arg.view.type !== 'dayGridMonth' && (
|
||||
{arg.view.type !== CALENDAR_VIEWS.dayGridMonth && (
|
||||
<span
|
||||
className={`fc-daygrid-day-number ${arg.isToday ? 'current-date' : ''}`}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
||||
import SettingsPage from '@/features/Settings/SettingsPage'
|
||||
import { useScreenSizeDetection } from '@/useScreenSizeDetection'
|
||||
import { getViewRange } from '@/utils/dateUtils'
|
||||
import type { CalendarApi } from '@fullcalendar/core'
|
||||
import CozyBridge from 'cozy-external-bridge'
|
||||
@@ -8,6 +9,8 @@ import { ErrorSnackbar } from '../Error/ErrorSnackbar'
|
||||
import { refreshCalendars } from '../Event/utils/eventUtils'
|
||||
import { Menubar, MenubarProps } from '../Menubar/Menubar'
|
||||
import CalendarApp from './Calendar'
|
||||
import { CALENDAR_VIEWS } from './utils/constants'
|
||||
import { setView } from '@/features/Settings/SettingsSlice'
|
||||
|
||||
export default function CalendarLayout() {
|
||||
const calendarRef = useRef<CalendarApi | null>(null)
|
||||
@@ -16,8 +19,31 @@ export default function CalendarLayout() {
|
||||
const selectedCalendars = useAppSelector(state => state.calendars.list)
|
||||
const tempcalendars = useAppSelector(state => state.calendars.templist)
|
||||
const view = useAppSelector(state => state.settings.view)
|
||||
|
||||
const { isTablet } = useScreenSizeDetection()
|
||||
const [openSidebar, setOpenSideBar] = useState(false)
|
||||
|
||||
const [currentDate, setCurrentDate] = useState<Date>(new Date())
|
||||
const [currentView, setCurrentView] = useState<string>('timeGridWeek')
|
||||
const [currentView, setCurrentView] = useState<string>(
|
||||
isTablet ? CALENDAR_VIEWS.timeGridDay : CALENDAR_VIEWS.timeGridWeek
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const setView = () =>
|
||||
setCurrentView(prev => {
|
||||
if (
|
||||
prev !== CALENDAR_VIEWS.timeGridDay &&
|
||||
prev !== CALENDAR_VIEWS.timeGridWeek
|
||||
) {
|
||||
return prev
|
||||
}
|
||||
|
||||
return isTablet
|
||||
? CALENDAR_VIEWS.timeGridDay
|
||||
: CALENDAR_VIEWS.timeGridWeek
|
||||
})
|
||||
setView()
|
||||
}, [isTablet])
|
||||
const isInIframe = useMemo(() => new CozyBridge().isInIframe(), [])
|
||||
|
||||
const handleRefresh = async () => {
|
||||
@@ -47,8 +73,18 @@ export default function CalendarLayout() {
|
||||
setCurrentDate(date)
|
||||
}
|
||||
|
||||
const handleViewChange = (view: string) => {
|
||||
const handleViewChange = (view: string): void => {
|
||||
if (!calendarRef.current) return
|
||||
dispatch(setView('calendar'))
|
||||
|
||||
calendarRef.current.changeView(view)
|
||||
|
||||
// Notify parent about view change
|
||||
setCurrentView(view)
|
||||
|
||||
// Notify parent about date change after view change
|
||||
const newDate = calendarRef.current.getDate()
|
||||
handleDateChange(newDate)
|
||||
}
|
||||
|
||||
// Hide topbar navigation elements when in settings view (same as fullscreen dialog mode)
|
||||
@@ -72,7 +108,8 @@ export default function CalendarLayout() {
|
||||
onDateChange: handleDateChange,
|
||||
currentView,
|
||||
onViewChange: handleViewChange,
|
||||
isIframe: isInIframe
|
||||
isIframe: isInIframe,
|
||||
onToggleSidebar: () => setOpenSideBar(true)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -84,6 +121,10 @@ export default function CalendarLayout() {
|
||||
onDateChange={handleDateChange}
|
||||
onViewChange={handleViewChange}
|
||||
menubarProps={menubarProps}
|
||||
openSidebar={openSidebar}
|
||||
onCloseSidebar={() => setOpenSideBar(false)}
|
||||
setCurrentView={setCurrentView}
|
||||
currentView={currentView}
|
||||
/>
|
||||
)}
|
||||
{view === 'settings' && <SettingsPage isInIframe={isInIframe} />}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { CALENDAR_VIEWS } from './utils/constants'
|
||||
|
||||
export function MiniCalendar({
|
||||
calendarRef,
|
||||
@@ -70,11 +71,11 @@ export function MiniCalendar({
|
||||
|
||||
const isToday = date.getTime() === today.getTime()
|
||||
const isSelectedDay =
|
||||
calendarRef.current?.view.type === 'timeGridDay' &&
|
||||
calendarRef.current?.view.type === CALENDAR_VIEWS.timeGridDay &&
|
||||
date.getTime() === selected.getTime()
|
||||
|
||||
const isInSelectedWeek =
|
||||
calendarRef.current?.view.type === 'timeGridWeek' ||
|
||||
calendarRef.current?.view.type === CALENDAR_VIEWS.timeGridWeek ||
|
||||
calendarRef.current?.view.type === undefined
|
||||
? (() => {
|
||||
const startOfWeek = computeStartOfTheWeek(selected)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { CalendarApi } from '@fullcalendar/core'
|
||||
import { Box, Button, Drawer, radius } from '@linagora/twake-mui'
|
||||
import AddIcon from '@mui/icons-material/Add'
|
||||
import CalendarViewDayOutlinedIcon from '@mui/icons-material/CalendarViewDayOutlined'
|
||||
import CalendarViewMonthOutlinedIcon from '@mui/icons-material/CalendarViewMonthOutlined'
|
||||
import CalendarViewWeekOutlinedIcon from '@mui/icons-material/CalendarViewWeekOutlined'
|
||||
import { Dispatch, MutableRefObject, SetStateAction } from 'react'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { User } from '../Attendees/PeopleSearch'
|
||||
import { FieldWithLabel } from '../Event/components/FieldWithLabel'
|
||||
import CalendarSelection from './CalendarSelection'
|
||||
import { MiniCalendar } from './MiniCalendar'
|
||||
import { TempCalendarsInput } from './TempCalendarsInput'
|
||||
|
||||
interface CalendarSidebarProps {
|
||||
isTablet: boolean
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
calendarRef: MutableRefObject<CalendarApi | null>
|
||||
isIframe?: boolean
|
||||
onCreateEvent: () => void
|
||||
onViewChange: (view: string) => void
|
||||
selectedMiniDate: Date
|
||||
setSelectedMiniDate: (date: Date) => void
|
||||
selectedCalendars: string[]
|
||||
setSelectedCalendars: Dispatch<SetStateAction<string[]>>
|
||||
tempUsers: User[]
|
||||
setTempUsers: Dispatch<SetStateAction<User[]>>
|
||||
}
|
||||
|
||||
export default function Sidebar({
|
||||
isTablet,
|
||||
open,
|
||||
onClose,
|
||||
calendarRef,
|
||||
isIframe,
|
||||
onCreateEvent,
|
||||
onViewChange,
|
||||
selectedMiniDate,
|
||||
setSelectedMiniDate,
|
||||
selectedCalendars,
|
||||
setSelectedCalendars,
|
||||
tempUsers,
|
||||
setTempUsers
|
||||
}: CalendarSidebarProps) {
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant={isTablet ? 'temporary' : 'permanent'}
|
||||
open={isTablet ? open : true}
|
||||
onClose={onClose}
|
||||
className="sidebar"
|
||||
sx={{
|
||||
[`& .MuiDrawer-paper`]: {
|
||||
paddingTop: isTablet ? 2 : 0,
|
||||
paddingBottom: 3,
|
||||
paddingLeft: 3,
|
||||
paddingRight: 2,
|
||||
width: '270px',
|
||||
marginTop: isTablet ? 0 : '70px'
|
||||
},
|
||||
zIndex: isTablet ? 3000 : 5
|
||||
}}
|
||||
slotProps={{ paper: { className: 'sidebar' } }}
|
||||
>
|
||||
{!isTablet && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 10,
|
||||
backgroundColor: '#fff',
|
||||
paddingTop: isIframe ? '10px' : 3
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="medium"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onCreateEvent}
|
||||
sx={{
|
||||
borderRadius: radius.lg,
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 'normal'
|
||||
}}
|
||||
>
|
||||
<AddIcon sx={{ marginRight: 0.5, fontSize: '20px' }} />{' '}
|
||||
{t('event.createEvent')}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<MiniCalendar
|
||||
calendarRef={calendarRef}
|
||||
selectedDate={selectedMiniDate}
|
||||
setSelectedMiniDate={setSelectedMiniDate}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isTablet && (
|
||||
<FieldWithLabel label={t('sidebar.displayMode')} isExpanded={false}>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => onViewChange('timeGridDay')}
|
||||
startIcon={<CalendarViewDayOutlinedIcon />}
|
||||
>
|
||||
{t('menubar.views.day')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
onViewChange('timeGridWeek')
|
||||
}}
|
||||
startIcon={<CalendarViewWeekOutlinedIcon />}
|
||||
>
|
||||
{t('menubar.views.week')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => onViewChange('dayGridMonth')}
|
||||
startIcon={<CalendarViewMonthOutlinedIcon />}
|
||||
>
|
||||
{t('menubar.views.month')}
|
||||
</Button>
|
||||
</FieldWithLabel>
|
||||
)}
|
||||
|
||||
<Box sx={{ mb: 3, mt: 2 }}>
|
||||
<TempCalendarsInput
|
||||
tempUsers={tempUsers}
|
||||
setTempUsers={setTempUsers}
|
||||
handleToggleEventPreview={onCreateEvent}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box className="calendarList">
|
||||
<CalendarSelection
|
||||
selectedCalendars={selectedCalendars}
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
/>
|
||||
</Box>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Box, Button, Drawer, radius } from '@linagora/twake-mui'
|
||||
import AddIcon from '@mui/icons-material/Add'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { MiniCalendar } from '../MiniCalendar'
|
||||
import { CalendarSidebarProps } from './SideBar'
|
||||
import { SidebarCommonContent } from './SidebarCommonContent'
|
||||
|
||||
export function DesktopSidebar({
|
||||
calendarRef,
|
||||
isIframe,
|
||||
onCreateEvent,
|
||||
selectedMiniDate,
|
||||
setSelectedMiniDate,
|
||||
tempUsers,
|
||||
setTempUsers,
|
||||
selectedCalendars,
|
||||
setSelectedCalendars
|
||||
}: CalendarSidebarProps) {
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
open
|
||||
className="sidebar"
|
||||
sx={{
|
||||
[`& .MuiDrawer-paper`]: {
|
||||
paddingTop: 0,
|
||||
paddingBottom: 3,
|
||||
paddingLeft: 3,
|
||||
paddingRight: 2,
|
||||
width: '270px',
|
||||
marginTop: '70px'
|
||||
},
|
||||
zIndex: 5
|
||||
}}
|
||||
slotProps={{ paper: { className: 'sidebar' } }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 10,
|
||||
backgroundColor: '#fff',
|
||||
paddingTop: isIframe ? '10px' : 3
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="medium"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={onCreateEvent}
|
||||
sx={{
|
||||
borderRadius: radius.lg,
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 'normal'
|
||||
}}
|
||||
>
|
||||
<AddIcon sx={{ marginRight: 0.5, fontSize: '20px' }} />{' '}
|
||||
{t('event.createEvent')}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<MiniCalendar
|
||||
calendarRef={calendarRef}
|
||||
selectedDate={selectedMiniDate}
|
||||
setSelectedMiniDate={setSelectedMiniDate}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<SidebarCommonContent
|
||||
onCreateEvent={onCreateEvent}
|
||||
tempUsers={tempUsers}
|
||||
setTempUsers={setTempUsers}
|
||||
selectedCalendars={selectedCalendars}
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
/>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useScreenSizeDetection } from '@/useScreenSizeDetection'
|
||||
import { CalendarApi } from '@fullcalendar/core'
|
||||
import { Dispatch, MutableRefObject, SetStateAction } from 'react'
|
||||
import { User } from '../../Attendees/PeopleSearch'
|
||||
import { DesktopSidebar } from './DesktopSidebar'
|
||||
import { TabletSidebar } from './TabletSidebar'
|
||||
|
||||
export interface CalendarSidebarProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
calendarRef: MutableRefObject<CalendarApi | null>
|
||||
isIframe?: boolean
|
||||
onCreateEvent: () => void
|
||||
onViewChange: (view: string) => void
|
||||
selectedMiniDate: Date
|
||||
setSelectedMiniDate: (date: Date) => void
|
||||
selectedCalendars: string[]
|
||||
setSelectedCalendars: Dispatch<SetStateAction<string[]>>
|
||||
tempUsers: User[]
|
||||
setTempUsers: Dispatch<SetStateAction<User[]>>
|
||||
currentView: string
|
||||
}
|
||||
|
||||
export default function Sidebar(sharedProps: CalendarSidebarProps) {
|
||||
const { isTablet } = useScreenSizeDetection()
|
||||
|
||||
return isTablet ? (
|
||||
<TabletSidebar {...sharedProps} />
|
||||
) : (
|
||||
<DesktopSidebar {...sharedProps} />
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Box } from '@linagora/twake-mui'
|
||||
import CalendarSelection from '../CalendarSelection'
|
||||
import { TempCalendarsInput } from '../TempCalendarsInput'
|
||||
import { CalendarSidebarProps } from './SideBar'
|
||||
|
||||
export function SidebarCommonContent({
|
||||
onCreateEvent,
|
||||
tempUsers,
|
||||
setTempUsers,
|
||||
selectedCalendars,
|
||||
setSelectedCalendars
|
||||
}: Pick<
|
||||
CalendarSidebarProps,
|
||||
| 'onCreateEvent'
|
||||
| 'tempUsers'
|
||||
| 'setTempUsers'
|
||||
| 'selectedCalendars'
|
||||
| 'setSelectedCalendars'
|
||||
>) {
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ mb: 3, mt: 2 }}>
|
||||
<TempCalendarsInput
|
||||
tempUsers={tempUsers}
|
||||
setTempUsers={setTempUsers}
|
||||
handleToggleEventPreview={onCreateEvent}
|
||||
/>
|
||||
</Box>
|
||||
<Box className="calendarList">
|
||||
<CalendarSelection
|
||||
selectedCalendars={selectedCalendars}
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
Drawer,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
useTheme
|
||||
} from '@linagora/twake-mui'
|
||||
import CalendarViewDayOutlinedIcon from '@mui/icons-material/CalendarViewDayOutlined'
|
||||
import CalendarViewMonthOutlinedIcon from '@mui/icons-material/CalendarViewMonthOutlined'
|
||||
import CalendarViewWeekOutlinedIcon from '@mui/icons-material/CalendarViewWeekOutlined'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { FieldWithLabel } from '../../Event/components/FieldWithLabel'
|
||||
import { CALENDAR_VIEWS } from '../utils/constants'
|
||||
import { CalendarSidebarProps } from './SideBar'
|
||||
import { SidebarCommonContent } from './SidebarCommonContent'
|
||||
|
||||
const VIEW_OPTIONS = [
|
||||
{
|
||||
label: 'menubar.views.day',
|
||||
value: CALENDAR_VIEWS.timeGridDay,
|
||||
icon: <CalendarViewDayOutlinedIcon />
|
||||
},
|
||||
{
|
||||
label: 'menubar.views.week',
|
||||
value: CALENDAR_VIEWS.timeGridWeek,
|
||||
icon: <CalendarViewWeekOutlinedIcon />
|
||||
},
|
||||
{
|
||||
label: 'menubar.views.month',
|
||||
value: CALENDAR_VIEWS.dayGridMonth,
|
||||
icon: <CalendarViewMonthOutlinedIcon />
|
||||
}
|
||||
]
|
||||
|
||||
export function TabletSidebar({
|
||||
open,
|
||||
onClose,
|
||||
onViewChange,
|
||||
onCreateEvent,
|
||||
tempUsers,
|
||||
setTempUsers,
|
||||
selectedCalendars,
|
||||
setSelectedCalendars,
|
||||
currentView
|
||||
}: CalendarSidebarProps) {
|
||||
const { t } = useI18n()
|
||||
const theme = useTheme()
|
||||
|
||||
const changeViewAndClose = (view: string): void => {
|
||||
onViewChange(view)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant="temporary"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
className="sidebar"
|
||||
sx={{
|
||||
[`& .MuiDrawer-paper`]: {
|
||||
paddingTop: 2,
|
||||
paddingBottom: 3,
|
||||
paddingLeft: 3,
|
||||
paddingRight: 2,
|
||||
width: '270px',
|
||||
marginTop: 0
|
||||
},
|
||||
zIndex: 3000
|
||||
}}
|
||||
slotProps={{ paper: { className: 'sidebar' } }}
|
||||
>
|
||||
<FieldWithLabel label={t('sidebar.displayMode')} isExpanded={false}>
|
||||
<MenuList>
|
||||
{VIEW_OPTIONS.map(option => {
|
||||
const isSelected = option.value === currentView
|
||||
return (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
selected={isSelected}
|
||||
onClick={() => changeViewAndClose(option.value)}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
color: isSelected
|
||||
? theme.palette.primary.main
|
||||
: theme.palette.text.primary
|
||||
}}
|
||||
>
|
||||
{option.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t(option.label)}
|
||||
primaryTypographyProps={{
|
||||
sx: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
lineHeight: '20px',
|
||||
color: isSelected
|
||||
? theme.palette.primary.main
|
||||
: theme.palette.text.primary
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</MenuList>
|
||||
</FieldWithLabel>
|
||||
|
||||
<SidebarCommonContent
|
||||
onCreateEvent={onCreateEvent}
|
||||
tempUsers={tempUsers}
|
||||
setTempUsers={setTempUsers}
|
||||
selectedCalendars={selectedCalendars}
|
||||
setSelectedCalendars={setSelectedCalendars}
|
||||
/>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export const createEventHandlers = (props: EventHandlersProps) => {
|
||||
timezone
|
||||
} = props
|
||||
|
||||
const handleDateSelect = (selectInfo: DateSelectArg) => {
|
||||
const handleDateSelect = (selectInfo: DateSelectArg | null) => {
|
||||
setSelectedRange(selectInfo)
|
||||
if (tempUsers) {
|
||||
const newEvent: CalendarEvent = {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ViewMountArg
|
||||
} from '@fullcalendar/core'
|
||||
import React from 'react'
|
||||
import { CALENDAR_VIEWS } from '../utils/constants'
|
||||
import { createMouseHandlers } from './mouseHandlers'
|
||||
|
||||
export interface ViewHandlersProps {
|
||||
@@ -54,16 +55,16 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
|
||||
}
|
||||
|
||||
const handleDayHeaderDidMount = (arg: DayHeaderMountArg) => {
|
||||
if (arg.view.type === 'timeGridWeek') {
|
||||
if (arg.view.type === CALENDAR_VIEWS.timeGridWeek) {
|
||||
const headerEl = arg.el
|
||||
|
||||
const handleDayHeaderClick = () => {
|
||||
calendarRef.current?.changeView('timeGridDay', arg.date)
|
||||
calendarRef.current?.changeView(CALENDAR_VIEWS.timeGridDay, arg.date)
|
||||
setSelectedDate(new Date(arg.date))
|
||||
setSelectedMiniDate(new Date(arg.date))
|
||||
|
||||
if (onViewChange) {
|
||||
onViewChange('timeGridDay')
|
||||
onViewChange(CALENDAR_VIEWS.timeGridDay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +82,10 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
|
||||
}
|
||||
|
||||
const handleViewDidMount = (arg: ViewMountArg) => {
|
||||
if (arg.view.type === 'timeGridWeek' || arg.view.type === 'timeGridDay') {
|
||||
if (
|
||||
arg.view.type === CALENDAR_VIEWS.timeGridWeek ||
|
||||
arg.view.type === CALENDAR_VIEWS.timeGridDay
|
||||
) {
|
||||
const calendarEl = document.querySelector('.fc') as HTMLElement
|
||||
if (calendarEl) {
|
||||
const mouseHandlers = createMouseHandlers({ calendarEl })
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const CALENDAR_VIEWS = {
|
||||
dayGridMonth: 'dayGridMonth',
|
||||
timeGridWeek: 'timeGridWeek',
|
||||
timeGridDay: 'timeGridDay'
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CALENDAR_VIEWS } from '@/components/Calendar/utils/constants'
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
@@ -76,7 +77,7 @@ export function EventChip({
|
||||
}
|
||||
|
||||
// View and time calculations
|
||||
const isMonthView = arg.view.type === 'dayGridMonth'
|
||||
const isMonthView = arg.view.type === CALENDAR_VIEWS.dayGridMonth
|
||||
const timeZone = arg.view.calendar?.getOption('timeZone') || 'UTC'
|
||||
const { startTime, endTime } = getEventTimes(event, timeZone)
|
||||
const eventLength = getEventDuration(event)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Box, Typography } from '@linagora/twake-mui'
|
||||
|
||||
export type AppIconProps = {
|
||||
name: string
|
||||
link: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export function AppIcon({ prop }: { prop: AppIconProps }) {
|
||||
return (
|
||||
<Box
|
||||
component="a"
|
||||
href={prop.link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
sx={{
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
p: '8px 12px 5px',
|
||||
borderRadius: '14px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.hover'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={prop.icon}
|
||||
alt={prop.name}
|
||||
sx={{ maxWidth: 42, height: 42 }}
|
||||
/>
|
||||
<Typography sx={{ mt: 0.75, textAlign: 'center', fontSize: 12 }}>
|
||||
{prop.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { IconButton, Popover } from '@linagora/twake-mui'
|
||||
import WidgetsOutlinedIcon from '@mui/icons-material/WidgetsOutlined'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { AppIcon, AppIconProps } from './AppIcon'
|
||||
|
||||
export function AppListMenu({
|
||||
anchorEl,
|
||||
onAppMenuOpen,
|
||||
onAppMenuClose
|
||||
}: {
|
||||
anchorEl: HTMLElement | null
|
||||
onAppMenuOpen: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
|
||||
onAppMenuClose: () => void
|
||||
}) {
|
||||
const { t } = useI18n()
|
||||
|
||||
const applist: AppIconProps[] = window.appList ?? []
|
||||
if (!(applist.length > 0)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={onAppMenuOpen}
|
||||
style={{ marginRight: 8 }}
|
||||
aria-label={t('menubar.apps')}
|
||||
title={t('menubar.apps')}
|
||||
>
|
||||
<WidgetsOutlinedIcon />
|
||||
</IconButton>
|
||||
|
||||
<Popover
|
||||
open={Boolean(anchorEl)}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onAppMenuClose}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: { minWidth: 230, mt: 2, p: '14px 8px', borderRadius: '14px' }
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="app-grid">
|
||||
{applist.map((prop: AppIconProps) => (
|
||||
<AppIcon key={prop.name} prop={prop} />
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { IconButton } from '@linagora/twake-mui'
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline'
|
||||
import RefreshIcon from '@mui/icons-material/Refresh'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { AppListMenu } from './AppListMenu'
|
||||
import SearchBar from './EventSearchBar'
|
||||
import { MainTitle } from './MainTitle'
|
||||
import { SharedMenubarProps } from './Menubar'
|
||||
import { NavigationControls } from './NavigationControls'
|
||||
import { SelectView } from './SelectView'
|
||||
import { UserMenu } from './UserMenu'
|
||||
|
||||
export function DesktopMenubar({
|
||||
calendarRef,
|
||||
currentView,
|
||||
isIframe,
|
||||
dateLabel,
|
||||
supportLink,
|
||||
anchorEl,
|
||||
onAppMenuOpen,
|
||||
onAppMenuClose,
|
||||
onUserMenuOpen,
|
||||
onSettingsClick,
|
||||
onLogoutClick,
|
||||
onNavigate,
|
||||
onRefresh,
|
||||
onViewChange,
|
||||
onDateChange,
|
||||
user,
|
||||
userMenuAnchorEl,
|
||||
onUserMenuClose
|
||||
}: SharedMenubarProps) {
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
<header className="menubar">
|
||||
<div className="left-menu">
|
||||
{!isIframe && (
|
||||
<div className="menu-items">
|
||||
<MainTitle
|
||||
calendarRef={calendarRef}
|
||||
currentView={currentView}
|
||||
onViewChange={onViewChange}
|
||||
onDateChange={onDateChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="menu-items" style={{ marginLeft: '65px' }}>
|
||||
<NavigationControls onNavigate={onNavigate} />
|
||||
</div>
|
||||
|
||||
<div className="menu-items">
|
||||
<div className="current-date-time">
|
||||
<p>{dateLabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="right-menu">
|
||||
<div className="search-container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
|
||||
<div className="menu-items">
|
||||
<IconButton
|
||||
className="refresh-button"
|
||||
onClick={onRefresh}
|
||||
aria-label={t('menubar.refresh')}
|
||||
title={t('menubar.refresh')}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<div className="menu-items">
|
||||
<SelectView currentView={currentView} onViewChange={onViewChange} />
|
||||
</div>
|
||||
|
||||
{!isIframe && (
|
||||
<>
|
||||
{supportLink && (
|
||||
<div className="menu-items">
|
||||
<IconButton
|
||||
component="a"
|
||||
href={supportLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ marginRight: 8 }}
|
||||
aria-label={t('menubar.help')}
|
||||
title={t('menubar.help')}
|
||||
>
|
||||
<HelpOutlineIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="menu-items">
|
||||
<AppListMenu
|
||||
anchorEl={anchorEl}
|
||||
onAppMenuOpen={onAppMenuOpen}
|
||||
onAppMenuClose={onAppMenuClose}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="menu-items">
|
||||
<UserMenu
|
||||
anchorEl={userMenuAnchorEl}
|
||||
onClose={onUserMenuClose}
|
||||
onSettingsClick={onSettingsClick}
|
||||
onLogoutClick={onLogoutClick}
|
||||
onUserMenuOpen={onUserMenuOpen}
|
||||
isIframe={isIframe}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useAppDispatch } from '@/app/hooks'
|
||||
import { setView } from '@/features/Settings/SettingsSlice'
|
||||
import logo from '@/static/header-logo.svg'
|
||||
import { CalendarApi } from '@fullcalendar/core'
|
||||
import { Button } from '@linagora/twake-mui'
|
||||
import React from 'react'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { CALENDAR_VIEWS } from '../Calendar/utils/constants'
|
||||
|
||||
export type MainTitleProps = {
|
||||
calendarRef: React.RefObject<CalendarApi | null>
|
||||
currentView: string
|
||||
onViewChange?: (view: string) => void
|
||||
onDateChange?: (date: Date) => void
|
||||
}
|
||||
|
||||
export function MainTitle({
|
||||
calendarRef,
|
||||
currentView,
|
||||
onViewChange,
|
||||
onDateChange
|
||||
}: MainTitleProps) {
|
||||
const { t } = useI18n()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const handleLogoClick = async () => {
|
||||
if (!calendarRef.current) return
|
||||
|
||||
await dispatch(setView('calendar'))
|
||||
|
||||
if (currentView !== CALENDAR_VIEWS.timeGridWeek) {
|
||||
calendarRef.current.changeView(CALENDAR_VIEWS.timeGridWeek)
|
||||
if (onViewChange) {
|
||||
onViewChange(CALENDAR_VIEWS.timeGridWeek)
|
||||
}
|
||||
}
|
||||
|
||||
calendarRef.current.today()
|
||||
|
||||
if (onDateChange) {
|
||||
const newDate = calendarRef.current.getDate()
|
||||
onDateChange(newDate)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="menubar-item tc-home">
|
||||
<Button
|
||||
onClick={handleLogoClick}
|
||||
aria-label={t('menubar.logoAlt')}
|
||||
style={{ background: 'none', border: 0, padding: 0, cursor: 'pointer' }}
|
||||
>
|
||||
<img
|
||||
className="logo"
|
||||
src={logo}
|
||||
alt={t('menubar.logoAlt')}
|
||||
onClick={handleLogoClick}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -44,7 +44,6 @@
|
||||
.menubar-item
|
||||
display flex
|
||||
align-items center
|
||||
margin-right 65px
|
||||
|
||||
.tc-home
|
||||
cursor pointer
|
||||
|
||||
@@ -1,37 +1,16 @@
|
||||
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
||||
import { setView } from '@/features/Settings/SettingsSlice'
|
||||
import { Logout } from '@/features/User/oidcAuth'
|
||||
import logo from '@/static/header-logo.svg'
|
||||
import { getInitials, stringToGradient } from '@/utils/avatarUtils'
|
||||
import { getUserDisplayName } from '@/utils/userUtils'
|
||||
import { userData } from '@/features/User/userDataTypes'
|
||||
import { useScreenSizeDetection } from '@/useScreenSizeDetection'
|
||||
import { redirectTo } from '@/utils/navigation'
|
||||
import { CalendarApi } from '@fullcalendar/core'
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Divider,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
Select,
|
||||
Typography
|
||||
} from '@linagora/twake-mui'
|
||||
import WidgetsOutlinedIcon from '@mui/icons-material/WidgetsOutlined'
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline'
|
||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
||||
import LogoutIcon from '@mui/icons-material/Logout'
|
||||
import RefreshIcon from '@mui/icons-material/Refresh'
|
||||
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { push } from 'redux-first-history'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import SearchBar from './EventSearchBar'
|
||||
import { DesktopMenubar } from './DesktopMenubar'
|
||||
import './Menubar.styl'
|
||||
import { TabletMenubar } from './TabletMenubar'
|
||||
|
||||
export type AppIconProps = {
|
||||
name: string
|
||||
@@ -47,26 +26,53 @@ export type MenubarProps = {
|
||||
currentView: string
|
||||
onViewChange?: (view: string) => void
|
||||
isIframe?: boolean
|
||||
onToggleSidebar: () => void
|
||||
}
|
||||
|
||||
export function Menubar({
|
||||
export type SharedMenubarProps = MenubarProps & {
|
||||
dateLabel: string
|
||||
supportLink: string | undefined
|
||||
anchorEl: HTMLElement | null
|
||||
onAppMenuOpen: (event: React.MouseEvent<HTMLElement>) => void
|
||||
onAppMenuClose: () => void
|
||||
userMenuAnchorEl: HTMLElement | null
|
||||
onUserMenuOpen: (event: React.MouseEvent<HTMLElement>) => void
|
||||
onUserMenuClose: () => void
|
||||
onSettingsClick: () => void
|
||||
onLogoutClick: () => void
|
||||
onNavigate: (action: 'prev' | 'next' | 'today') => void
|
||||
onViewChange: (view: string) => void
|
||||
user: userData | null
|
||||
}
|
||||
|
||||
export const Menubar: React.FC<MenubarProps> = ({
|
||||
calendarRef,
|
||||
onRefresh,
|
||||
currentDate,
|
||||
onDateChange,
|
||||
currentView,
|
||||
onViewChange,
|
||||
isIframe
|
||||
}: MenubarProps) {
|
||||
isIframe,
|
||||
onToggleSidebar
|
||||
}) => {
|
||||
const { t } = useI18n() // deliberately NOT using f()
|
||||
|
||||
const user = useAppSelector(state => state.user.userData)
|
||||
const applist: AppIconProps[] = window.appList ?? []
|
||||
const supportLink = window.SUPPORT_URL
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||
const [userMenuAnchorEl, setUserMenuAnchorEl] = useState<null | HTMLElement>(
|
||||
null
|
||||
)
|
||||
const dispatch = useAppDispatch()
|
||||
const { isTablet } = useScreenSizeDetection()
|
||||
|
||||
useEffect(() => {
|
||||
const resetMenuAnchorsOnResize = (): void => {
|
||||
setAnchorEl(null)
|
||||
setUserMenuAnchorEl(null)
|
||||
}
|
||||
resetMenuAnchorsOnResize()
|
||||
}, [isTablet])
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
@@ -77,13 +83,6 @@ export function Menubar({
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
const handleOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const handleNavigation = async (action: 'prev' | 'next' | 'today') => {
|
||||
if (!calendarRef.current) return
|
||||
@@ -108,29 +107,19 @@ export function Menubar({
|
||||
}
|
||||
|
||||
const handleViewChange = async (view: string) => {
|
||||
if (!calendarRef.current) return
|
||||
await dispatch(setView('calendar'))
|
||||
|
||||
calendarRef.current.changeView(view)
|
||||
|
||||
// Notify parent about view change
|
||||
if (onViewChange) {
|
||||
onViewChange(view)
|
||||
}
|
||||
|
||||
// Notify parent about date change after view change
|
||||
if (onDateChange) {
|
||||
const newDate = calendarRef.current.getDate()
|
||||
onDateChange(newDate)
|
||||
}
|
||||
}
|
||||
|
||||
const open = Boolean(anchorEl)
|
||||
const userMenuOpen = Boolean(userMenuAnchorEl)
|
||||
const handleAppMenuOpen = (event: React.MouseEvent<HTMLElement>) =>
|
||||
setAnchorEl(event.currentTarget)
|
||||
|
||||
const handleUserMenuClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
const handleAppMenuClose = () => setAnchorEl(null)
|
||||
|
||||
const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>) =>
|
||||
setUserMenuAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleUserMenuClose = () => {
|
||||
setUserMenuAnchorEl(null)
|
||||
@@ -154,341 +143,32 @@ export function Menubar({
|
||||
const monthName = t(`months.standalone.${monthIndex}`)
|
||||
const dateLabel = `${monthName} ${year}`
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="menubar">
|
||||
<div className="left-menu">
|
||||
{!isIframe && (
|
||||
<div className="menu-items">
|
||||
<MainTitle
|
||||
calendarRef={calendarRef}
|
||||
currentView={currentView}
|
||||
onViewChange={onViewChange}
|
||||
onDateChange={onDateChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="menu-items">
|
||||
<div className="navigation-controls">
|
||||
<ButtonGroup
|
||||
variant="outlined"
|
||||
size="medium"
|
||||
sx={{
|
||||
'& button:first-of-type': {
|
||||
borderRadius: '12px 0 0 12px'
|
||||
},
|
||||
'& button:last-of-type': {
|
||||
borderRadius: '0 12px 12px 0'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
sx={{ width: 20 }}
|
||||
onClick={() => handleNavigation('prev')}
|
||||
>
|
||||
<ChevronLeftIcon sx={{ height: 20 }} />
|
||||
</Button>
|
||||
<Button onClick={() => handleNavigation('today')}>
|
||||
{t('menubar.today')}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ width: 20 }}
|
||||
onClick={() => handleNavigation('next')}
|
||||
>
|
||||
<ChevronRightIcon sx={{ height: 20 }} />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
<div className="current-date-time">
|
||||
<p>{dateLabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-menu">
|
||||
<div className="search-container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
<IconButton
|
||||
className="refresh-button"
|
||||
onClick={onRefresh}
|
||||
aria-label={t('menubar.refresh')}
|
||||
title={t('menubar.refresh')}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
<FormControl
|
||||
size="small"
|
||||
style={{ minWidth: 120, marginRight: 16 }}
|
||||
className="select-display"
|
||||
>
|
||||
<Select
|
||||
value={currentView}
|
||||
onChange={e => handleViewChange(e.target.value)}
|
||||
variant="outlined"
|
||||
aria-label={t('menubar.viewSelector')}
|
||||
sx={{
|
||||
borderRadius: '12px',
|
||||
marginLeft: 1,
|
||||
'& fieldset': { borderRadius: '12px' }
|
||||
}}
|
||||
>
|
||||
<MenuItem value="dayGridMonth">
|
||||
{t('menubar.views.month')}
|
||||
</MenuItem>
|
||||
<MenuItem value="timeGridWeek">
|
||||
{t('menubar.views.week')}
|
||||
</MenuItem>
|
||||
<MenuItem value="timeGridDay">
|
||||
{t('menubar.views.day')}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
{!isIframe && (
|
||||
<>
|
||||
{supportLink && (
|
||||
<div className="menu-items">
|
||||
<IconButton
|
||||
component="a"
|
||||
href={supportLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ marginRight: 8 }}
|
||||
aria-label={t('menubar.help')}
|
||||
title={t('menubar.help')}
|
||||
>
|
||||
<HelpOutlineIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="menu-items">
|
||||
{applist.length > 0 && (
|
||||
<IconButton
|
||||
onClick={handleOpen}
|
||||
style={{ marginRight: 8 }}
|
||||
aria-label={t('menubar.apps')}
|
||||
title={t('menubar.apps')}
|
||||
>
|
||||
<WidgetsOutlinedIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="menu-items">
|
||||
<IconButton
|
||||
onClick={!isIframe ? handleUserMenuClick : handleSettingsClick}
|
||||
aria-label={isIframe ? t('menubar.settings') : undefined}
|
||||
>
|
||||
{!isIframe ? (
|
||||
<Avatar
|
||||
color={stringToGradient(getUserDisplayName(user))}
|
||||
size="m"
|
||||
aria-label={t('menubar.userProfile')}
|
||||
>
|
||||
{getInitials(getUserDisplayName(user))}
|
||||
</Avatar>
|
||||
) : (
|
||||
<SettingsOutlinedIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
minWidth: 230,
|
||||
mt: 2,
|
||||
p: '14px 8px',
|
||||
borderRadius: '14px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="app-grid">
|
||||
{applist.map((prop: AppIconProps) => (
|
||||
<AppIcon key={prop.name} prop={prop} />
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<Menu
|
||||
open={userMenuOpen}
|
||||
anchorEl={userMenuAnchorEl}
|
||||
onClose={handleUserMenuClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
minWidth: 280,
|
||||
mt: 1,
|
||||
padding: '0 !important',
|
||||
borderRadius: '14px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '24px'
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
color={stringToGradient(getUserDisplayName(user))}
|
||||
size="l"
|
||||
sx={{ marginBottom: '8px' }}
|
||||
>
|
||||
{getInitials(getUserDisplayName(user))}
|
||||
</Avatar>
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#424244',
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 22,
|
||||
fontWeight: 600
|
||||
}}
|
||||
>
|
||||
{getUserDisplayName(user)}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{user?.email}
|
||||
</Typography>
|
||||
</Box>
|
||||
<MenuItem onClick={handleSettingsClick} sx={{ py: 1.5 }}>
|
||||
<SettingsOutlinedIcon
|
||||
sx={{
|
||||
mr: 2,
|
||||
color: 'rgba(28, 27, 31, 0.48)',
|
||||
fontSize: 20
|
||||
}}
|
||||
/>
|
||||
{t('menubar.settings') || 'Settings'}
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleLogoutClick} sx={{ py: 1.5 }}>
|
||||
<LogoutIcon
|
||||
sx={{
|
||||
mr: 2,
|
||||
color: 'rgba(28, 27, 31, 0.48)',
|
||||
fontSize: 20
|
||||
}}
|
||||
/>
|
||||
{t('menubar.logout') || 'Logout'}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export type MainTitleProps = {
|
||||
calendarRef: React.RefObject<CalendarApi | null>
|
||||
currentView: string
|
||||
onViewChange?: (view: string) => void
|
||||
onDateChange?: (date: Date) => void
|
||||
}
|
||||
|
||||
export function MainTitle({
|
||||
calendarRef,
|
||||
currentView,
|
||||
onViewChange,
|
||||
onDateChange
|
||||
}: MainTitleProps) {
|
||||
const { t } = useI18n()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const handleLogoClick = async () => {
|
||||
if (!calendarRef.current) return
|
||||
|
||||
await dispatch(setView('calendar'))
|
||||
|
||||
if (currentView !== 'timeGridWeek') {
|
||||
calendarRef.current.changeView('timeGridWeek')
|
||||
if (onViewChange) {
|
||||
onViewChange('timeGridWeek')
|
||||
}
|
||||
}
|
||||
|
||||
calendarRef.current.today()
|
||||
|
||||
if (onDateChange) {
|
||||
const newDate = calendarRef.current.getDate()
|
||||
onDateChange(newDate)
|
||||
}
|
||||
const sharedProps: SharedMenubarProps = {
|
||||
calendarRef,
|
||||
onRefresh,
|
||||
currentDate,
|
||||
onDateChange,
|
||||
currentView,
|
||||
onViewChange: handleViewChange,
|
||||
isIframe,
|
||||
onToggleSidebar,
|
||||
dateLabel,
|
||||
supportLink,
|
||||
anchorEl,
|
||||
onAppMenuOpen: handleAppMenuOpen,
|
||||
onAppMenuClose: handleAppMenuClose,
|
||||
userMenuAnchorEl,
|
||||
onUserMenuOpen: handleUserMenuOpen,
|
||||
onUserMenuClose: handleUserMenuClose,
|
||||
onSettingsClick: handleSettingsClick,
|
||||
onLogoutClick: handleLogoutClick,
|
||||
onNavigate: handleNavigation,
|
||||
user
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="menubar-item tc-home">
|
||||
<img
|
||||
className="logo"
|
||||
src={logo}
|
||||
alt={t('menubar.logoAlt')}
|
||||
onClick={handleLogoClick}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AppIcon({ prop }: { prop: AppIconProps }) {
|
||||
return (
|
||||
<Box
|
||||
component="a"
|
||||
href={prop.link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
sx={{
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
p: '8px 12px 5px',
|
||||
borderRadius: '14px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.hover'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={prop.icon}
|
||||
alt={prop.name}
|
||||
sx={{ maxWidth: 42, height: 42 }}
|
||||
/>
|
||||
<Typography sx={{ mt: 0.75, textAlign: 'center', fontSize: 12 }}>
|
||||
{prop.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
return isTablet ? (
|
||||
<TabletMenubar {...sharedProps} />
|
||||
) : (
|
||||
<DesktopMenubar {...sharedProps} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Button, ButtonGroup } from '@linagora/twake-mui'
|
||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
|
||||
export function NavigationControls({
|
||||
onNavigate
|
||||
}: {
|
||||
onNavigate: (action: 'today' | 'next' | 'prev') => void
|
||||
}) {
|
||||
const { t } = useI18n()
|
||||
return (
|
||||
<div className="navigation-controls">
|
||||
<ButtonGroup
|
||||
variant="outlined"
|
||||
size="medium"
|
||||
sx={{
|
||||
'& button:first-of-type': { borderRadius: '12px 0 0 12px' },
|
||||
'& button:last-of-type': { borderRadius: '0 12px 12px 0' }
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
sx={{ width: 20 }}
|
||||
onClick={() => onNavigate('prev')}
|
||||
aria-label={t('menubar.prev')}
|
||||
title={t('menubar.prev')}
|
||||
>
|
||||
<ChevronLeftIcon sx={{ height: 20 }} />
|
||||
</Button>
|
||||
<Button onClick={() => onNavigate('today')}>
|
||||
{t('menubar.today')}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ width: 20 }}
|
||||
onClick={() => onNavigate('next')}
|
||||
aria-label={t('menubar.next')}
|
||||
title={t('menubar.next')}
|
||||
>
|
||||
<ChevronRightIcon sx={{ height: 20 }} />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { FormControl, MenuItem, Select } from '@linagora/twake-mui'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import { CALENDAR_VIEWS } from '../Calendar/utils/constants'
|
||||
|
||||
export function SelectView({
|
||||
currentView,
|
||||
onViewChange
|
||||
}: {
|
||||
currentView: string
|
||||
onViewChange: (view: string) => void
|
||||
}) {
|
||||
const { t } = useI18n()
|
||||
return (
|
||||
<FormControl
|
||||
size="small"
|
||||
style={{ minWidth: 120, marginRight: 16 }}
|
||||
className="select-display"
|
||||
>
|
||||
<Select
|
||||
value={currentView}
|
||||
onChange={e => onViewChange(e.target.value)}
|
||||
variant="outlined"
|
||||
aria-label={t('menubar.viewSelector')}
|
||||
sx={{
|
||||
borderRadius: '12px',
|
||||
marginLeft: 1,
|
||||
'& fieldset': { borderRadius: '12px' }
|
||||
}}
|
||||
>
|
||||
<MenuItem value={CALENDAR_VIEWS.dayGridMonth}>
|
||||
{t('menubar.views.month')}
|
||||
</MenuItem>
|
||||
<MenuItem value={CALENDAR_VIEWS.timeGridWeek}>
|
||||
{t('menubar.views.week')}
|
||||
</MenuItem>
|
||||
<MenuItem value={CALENDAR_VIEWS.timeGridDay}>
|
||||
{t('menubar.views.day')}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { IconButton, Stack } from '@linagora/twake-mui'
|
||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
||||
import MenuIcon from '@mui/icons-material/Menu'
|
||||
import RefreshIcon from '@mui/icons-material/Refresh'
|
||||
import TodayIcon from '@mui/icons-material/Today'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
import SearchBar from './EventSearchBar'
|
||||
import { MainTitle } from './MainTitle'
|
||||
import { SharedMenubarProps } from './Menubar'
|
||||
import { UserMenu } from './UserMenu'
|
||||
|
||||
export function TabletMenubar({
|
||||
calendarRef,
|
||||
currentView,
|
||||
isIframe,
|
||||
dateLabel,
|
||||
onUserMenuOpen,
|
||||
onSettingsClick,
|
||||
onLogoutClick,
|
||||
onNavigate,
|
||||
onRefresh,
|
||||
onToggleSidebar,
|
||||
user,
|
||||
userMenuAnchorEl,
|
||||
onUserMenuClose,
|
||||
onViewChange,
|
||||
onDateChange
|
||||
}: SharedMenubarProps) {
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
<header className="menubar">
|
||||
<div className="left-menu">
|
||||
<IconButton
|
||||
onClick={onToggleSidebar}
|
||||
aria-label={t('menubar.toggleSidebar')}
|
||||
title={t('menubar.toggleSidebar')}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
{!isIframe && (
|
||||
<div className="menu-items">
|
||||
<MainTitle
|
||||
calendarRef={calendarRef}
|
||||
currentView={currentView}
|
||||
onViewChange={onViewChange}
|
||||
onDateChange={onDateChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="menu-items" style={{ marginLeft: 0 }}>
|
||||
<div className="navigation-controls">
|
||||
<Stack direction="row">
|
||||
<IconButton
|
||||
onClick={() => onNavigate('prev')}
|
||||
aria-label={t('menubar.prev')}
|
||||
title={t('menubar.prev')}
|
||||
>
|
||||
<ChevronLeftIcon sx={{ height: 20 }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="primary"
|
||||
sx={{
|
||||
border: '1px solid',
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
onClick={() => onNavigate('today')}
|
||||
aria-label={t('menubar.today')}
|
||||
title={t('menubar.today')}
|
||||
>
|
||||
<TodayIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => onNavigate('next')}
|
||||
aria-label={t('menubar.next')}
|
||||
title={t('menubar.next')}
|
||||
>
|
||||
<ChevronRightIcon sx={{ height: 20 }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="menu-items">
|
||||
<div className="current-date-time">
|
||||
<p>{dateLabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="right-menu">
|
||||
<div className="search-container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
<div className="menu-items">
|
||||
<IconButton
|
||||
className="refresh-button"
|
||||
onClick={onRefresh}
|
||||
aria-label={t('menubar.refresh')}
|
||||
title={t('menubar.refresh')}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<div className="menu-items">
|
||||
<UserMenu
|
||||
anchorEl={userMenuAnchorEl}
|
||||
onClose={onUserMenuClose}
|
||||
onSettingsClick={onSettingsClick}
|
||||
onLogoutClick={onLogoutClick}
|
||||
onUserMenuOpen={onUserMenuOpen}
|
||||
isIframe={isIframe}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { userData } from '@/features/User/userDataTypes'
|
||||
import { getInitials, stringToGradient } from '@/utils/avatarUtils'
|
||||
import { getUserDisplayName } from '@/utils/userUtils'
|
||||
import {
|
||||
alpha,
|
||||
Avatar,
|
||||
Box,
|
||||
Divider,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Typography,
|
||||
useTheme
|
||||
} from '@linagora/twake-mui'
|
||||
import LogoutIcon from '@mui/icons-material/Logout'
|
||||
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'
|
||||
import { MouseEvent } from 'react'
|
||||
import { useI18n } from 'twake-i18n'
|
||||
|
||||
export type UserMenuProps = {
|
||||
anchorEl: HTMLElement | null
|
||||
onClose: () => void
|
||||
onSettingsClick: () => void
|
||||
onLogoutClick: () => void
|
||||
onUserMenuOpen: (event: MouseEvent<HTMLElement>) => void
|
||||
user: userData | null
|
||||
isIframe?: boolean
|
||||
}
|
||||
|
||||
export function UserMenu({
|
||||
anchorEl,
|
||||
onClose,
|
||||
onSettingsClick,
|
||||
onLogoutClick,
|
||||
onUserMenuOpen,
|
||||
user,
|
||||
isIframe = false
|
||||
}: UserMenuProps): JSX.Element {
|
||||
const { t } = useI18n()
|
||||
const theme = useTheme()
|
||||
const displayName = getUserDisplayName(user)
|
||||
|
||||
const open = Boolean(anchorEl)
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={!isIframe ? onUserMenuOpen : onSettingsClick}
|
||||
aria-label={isIframe ? t('menubar.settings') : t('menubar.userProfile')}
|
||||
title={isIframe ? t('menubar.settings') : t('menubar.userProfile')}
|
||||
>
|
||||
{!isIframe ? (
|
||||
<Avatar color={stringToGradient(displayName)} size="m">
|
||||
{getInitials(displayName)}
|
||||
</Avatar>
|
||||
) : (
|
||||
<SettingsOutlinedIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
minWidth: 280,
|
||||
mt: 1,
|
||||
padding: '0 !important',
|
||||
borderRadius: '14px'
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '24px'
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
color={stringToGradient(displayName)}
|
||||
size="l"
|
||||
sx={{ marginBottom: '8px' }}
|
||||
>
|
||||
{getInitials(displayName)}
|
||||
</Avatar>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey[900],
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 22,
|
||||
fontWeight: 600
|
||||
}}
|
||||
>
|
||||
{displayName}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: 14, fontWeight: 500 }}>
|
||||
{user?.email}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<MenuItem onClick={onSettingsClick} sx={{ py: 1.5 }}>
|
||||
<SettingsOutlinedIcon
|
||||
sx={{
|
||||
mr: 2,
|
||||
color: alpha(theme.palette.grey.A900, 0.48),
|
||||
fontSize: 20
|
||||
}}
|
||||
/>
|
||||
{t('menubar.settings') || 'Settings'}
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
<MenuItem onClick={onLogoutClick} sx={{ py: 1.5 }}>
|
||||
<LogoutIcon
|
||||
sx={{
|
||||
mr: 2,
|
||||
color: alpha(theme.palette.grey.A900, 0.48),
|
||||
fontSize: 20
|
||||
}}
|
||||
/>
|
||||
{t('menubar.logout') || 'Logout'}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
||||
import { CALENDAR_VIEWS } from '@/components/Calendar/utils/constants'
|
||||
import { getCalendarDetailAsync } from '@/features/Calendars/services'
|
||||
import {
|
||||
formatDateToYYYYMMDDTHHMMSS,
|
||||
@@ -78,7 +79,7 @@ export function useCalendarDataLoader({
|
||||
const { visibleStart, visibleEnd, prefetchEnd } = useMemo(() => {
|
||||
const { start, end } = getViewRange(selectedDate, currentView)
|
||||
const prefetchEnd =
|
||||
currentView === 'dayGridMonth'
|
||||
currentView === CALENDAR_VIEWS.dayGridMonth
|
||||
? end.getTime()
|
||||
: getAdjacentWeekRange(selectedDate).end.getTime()
|
||||
return {
|
||||
|
||||
@@ -230,7 +230,10 @@
|
||||
"duplicateEvent": "Duplicate event"
|
||||
},
|
||||
"menubar": {
|
||||
"toggleSidebar": "Toggle sidebar",
|
||||
"today": "Today",
|
||||
"next": "Next",
|
||||
"prev": "Previous",
|
||||
"refresh": "Refresh",
|
||||
"viewSelector": "Select view",
|
||||
"languageSelector": "Select language",
|
||||
@@ -378,5 +381,8 @@
|
||||
"searchError": "Unable to fetch resources right now. Please try again.",
|
||||
"noResults": "No results",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"sidebar": {
|
||||
"displayMode": "Display mode"
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -232,6 +232,8 @@
|
||||
},
|
||||
"menubar": {
|
||||
"today": "Aujourd'hui",
|
||||
"next": "Précédent",
|
||||
"prev": "Suivant",
|
||||
"refresh": "Actualiser",
|
||||
"viewSelector": "Sélectionner la vue",
|
||||
"languageSelector": "Sélectionner la langue",
|
||||
@@ -245,7 +247,8 @@
|
||||
"logoAlt": "Calendrier",
|
||||
"settings": "Paramètres",
|
||||
"help": "Aide",
|
||||
"logout": "Déconnexion"
|
||||
"logout": "Déconnexion",
|
||||
"toggleSidebar": "Afficher/masquer la barre latérale"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres",
|
||||
@@ -379,5 +382,8 @@
|
||||
"searchError": "Impossible de récupérer les ressources pour le moment. Veuillez réessayer.",
|
||||
"noResults": "Aucun résultat",
|
||||
"loading": "Chargement..."
|
||||
},
|
||||
"sidebar": {
|
||||
"displayMode": "Mode d'affichage"
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -232,6 +232,8 @@
|
||||
},
|
||||
"menubar": {
|
||||
"today": "Сегодня",
|
||||
"next": "Следующий",
|
||||
"prev": "Предыдущий",
|
||||
"refresh": "Обновить",
|
||||
"viewSelector": "Выбор вида",
|
||||
"languageSelector": "Язык",
|
||||
@@ -245,7 +247,8 @@
|
||||
"logoAlt": "Календарь",
|
||||
"settings": "Настройки",
|
||||
"help": "Помощь",
|
||||
"logout": "Выйти"
|
||||
"logout": "Выйти",
|
||||
"toggleSidebar": "Показать/скрыть боковую панель"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
@@ -379,5 +382,8 @@
|
||||
"searchError": "Не удалось получить ресурсы. Попробуйте снова.",
|
||||
"noResults": "Нет результатов",
|
||||
"loading": "Загрузка..."
|
||||
},
|
||||
"sidebar": {
|
||||
"displayMode": "Режим отображения"
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -230,6 +230,8 @@
|
||||
},
|
||||
"menubar": {
|
||||
"today": "Hôm nay",
|
||||
"next": "Tiếp theo",
|
||||
"prev": "Trước đó",
|
||||
"refresh": "Làm mới",
|
||||
"viewSelector": "Chọn chế độ xem",
|
||||
"languageSelector": "Chọn ngôn ngữ",
|
||||
@@ -243,7 +245,8 @@
|
||||
"logoAlt": "Lịch",
|
||||
"settings": "Cài đặt",
|
||||
"help": "Giúp đỡ",
|
||||
"logout": "Đăng xuất"
|
||||
"logout": "Đăng xuất",
|
||||
"toggleSidebar": "Ẩn/hiện thanh bên"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Cài đặt",
|
||||
@@ -377,5 +380,8 @@
|
||||
"searchError": "Không thể lấy tài nguyên. Vui lòng thử lại.",
|
||||
"noResults": "Không có kết quả",
|
||||
"loading": "Đang tải..."
|
||||
},
|
||||
"sidebar": {
|
||||
"displayMode": "Chế độ hiển thị"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ if (typeof window !== 'undefined') {
|
||||
window.WS_PING_PERIOD_MS = 5000
|
||||
window.WS_PING_TIMEOUT_PERIOD_MS = 5000
|
||||
}
|
||||
jest.mock('@/useScreenSizeDetection', () => ({
|
||||
useScreenSizeDetection: jest.fn(() => ({ isMobile: false, isTablet: false }))
|
||||
}))
|
||||
// Suppress jsdom CSS selector parsing errors for Emotion/MUI
|
||||
if (typeof window !== 'undefined' && window.getComputedStyle) {
|
||||
const originalGetComputedStyle = window.getComputedStyle
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CALENDAR_VIEWS } from '@/components/Calendar/utils/constants'
|
||||
import moment from 'moment'
|
||||
|
||||
export function formatDateToYYYYMMDDTHHMMSS(date: Date) {
|
||||
@@ -69,7 +70,7 @@ export function getViewRange(
|
||||
date = new Date(),
|
||||
view: string
|
||||
): { start: Date; end: Date } {
|
||||
if (view === 'dayGridMonth') {
|
||||
if (view === CALENDAR_VIEWS.dayGridMonth) {
|
||||
return getCalendarRange(date)
|
||||
}
|
||||
return computeWeekRange(date)
|
||||
|
||||
Reference in New Issue
Block a user