Merge pull request #752 from lethemanh/771-timezone-bottom-sheet
771 timezone bottom sheet
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { TimezoneSelector } from '@/components/Calendar/TimezoneSelector'
|
import { TimezoneSelector } from '@/components/Timezone/TimezoneSelector'
|
||||||
import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react'
|
import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { renderWithProviders } from '../../utils/Renderwithproviders'
|
import { renderWithProviders } from '../../utils/Renderwithproviders'
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import './Calendar.styl'
|
|||||||
import './CustomCalendar.styl'
|
import './CustomCalendar.styl'
|
||||||
import { useCalendarEventHandlers } from './hooks/useCalendarEventHandlers'
|
import { useCalendarEventHandlers } from './hooks/useCalendarEventHandlers'
|
||||||
import { useCalendarViewHandlers } from './hooks/useCalendarViewHandlers'
|
import { useCalendarViewHandlers } from './hooks/useCalendarViewHandlers'
|
||||||
import { TimezoneSelector } from './TimezoneSelector'
|
import { TimezoneSelector } from '../Timezone/TimezoneSelector'
|
||||||
import {
|
import {
|
||||||
eventToFullCalendarFormat,
|
eventToFullCalendarFormat,
|
||||||
extractEvents,
|
extractEvents,
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
import {
|
|
||||||
browserDefaultTimeZone,
|
|
||||||
getTimezoneOffset,
|
|
||||||
resolveTimezone
|
|
||||||
} from '@/utils/timezone'
|
|
||||||
import { TIMEZONES } from '@/utils/timezone-data'
|
|
||||||
import { Button, Popover } from '@linagora/twake-mui'
|
|
||||||
import { MouseEvent, useMemo, useRef, useState } from 'react'
|
|
||||||
import { useI18n } from 'twake-i18n'
|
|
||||||
import { TimezoneAutocomplete } from '../Timezone/TimezoneAutocomplete'
|
|
||||||
|
|
||||||
interface TimezoneSelectProps {
|
|
||||||
value: string
|
|
||||||
onChange: (value: string) => void
|
|
||||||
referenceDate: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TimezoneSelector({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
referenceDate
|
|
||||||
}: TimezoneSelectProps) {
|
|
||||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
|
||||||
|
|
||||||
const timezoneList = useTimeZoneList()
|
|
||||||
|
|
||||||
const effectiveTimezone = value
|
|
||||||
? resolveTimezone(value)
|
|
||||||
: timezoneList.browserTz
|
|
||||||
const selectedOffset = getTimezoneOffset(effectiveTimezone, referenceDate)
|
|
||||||
|
|
||||||
const handleOpen = (event: MouseEvent<HTMLElement>) => {
|
|
||||||
setAnchorEl(event.currentTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setAnchorEl(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const open = Boolean(anchorEl)
|
|
||||||
const { t } = useI18n()
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
variant="text"
|
|
||||||
size="small"
|
|
||||||
onClick={handleOpen}
|
|
||||||
sx={{
|
|
||||||
textTransform: 'none',
|
|
||||||
minWidth: 'auto',
|
|
||||||
padding: '2px 4px',
|
|
||||||
margin: 0,
|
|
||||||
lineHeight: 1.2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedOffset || t('common.select_timezone')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Popover
|
|
||||||
open={open}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
onClose={handleClose}
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: 'bottom',
|
|
||||||
horizontal: 'left'
|
|
||||||
}}
|
|
||||||
transformOrigin={{
|
|
||||||
vertical: 'top',
|
|
||||||
horizontal: 'left'
|
|
||||||
}}
|
|
||||||
slotProps={{
|
|
||||||
paper: {
|
|
||||||
sx: { width: 280, maxHeight: 400, overflow: 'hidden', p: 0 }
|
|
||||||
},
|
|
||||||
transition: {
|
|
||||||
onEntered: () => {
|
|
||||||
inputRef.current?.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TimezoneAutocomplete
|
|
||||||
size="medium"
|
|
||||||
value={effectiveTimezone}
|
|
||||||
onChange={onChange}
|
|
||||||
zones={timezoneList.zones}
|
|
||||||
getTimezoneOffset={(tzName: string) =>
|
|
||||||
getTimezoneOffset(tzName, referenceDate)
|
|
||||||
}
|
|
||||||
inputRef={inputRef}
|
|
||||||
openOnFocus
|
|
||||||
showIcon={false}
|
|
||||||
inputFontSize="14px"
|
|
||||||
inputPadding="2px 4px"
|
|
||||||
onClose={handleClose}
|
|
||||||
disableClearable={true}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTimeZoneList() {
|
|
||||||
return useMemo(() => {
|
|
||||||
const zones = Object.keys(TIMEZONES.zones).sort()
|
|
||||||
const browserTz = resolveTimezone(browserDefaultTimeZone)
|
|
||||||
|
|
||||||
return { zones, browserTz, getTimezoneOffset }
|
|
||||||
}, [])
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { getTimezoneOffset, resolveTimezone } from '@/utils/timezone'
|
||||||
|
import { Popover } from '@linagora/twake-mui'
|
||||||
|
import React, { useRef } from 'react'
|
||||||
|
import { TimezoneAutocomplete } from '../Timezone/TimezoneAutocomplete'
|
||||||
|
import { useTimeZoneList } from './hooks/useTimeZoneList'
|
||||||
|
import { TimezoneSelectProps } from './TimezoneSelector'
|
||||||
|
|
||||||
|
export const LargeTimezoneSelector: React.FC<
|
||||||
|
TimezoneSelectProps & {
|
||||||
|
onClose: () => void
|
||||||
|
open: boolean
|
||||||
|
anchorEl?: HTMLElement | null
|
||||||
|
}
|
||||||
|
> = ({ value, onChange, referenceDate, onClose, open, anchorEl }) => {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const timezoneList = useTimeZoneList()
|
||||||
|
|
||||||
|
const effectiveTimezone = value
|
||||||
|
? resolveTimezone(value)
|
||||||
|
: timezoneList.browserTz
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
onClose={onClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left'
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left'
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
paper: {
|
||||||
|
sx: { width: 280, maxHeight: 400, overflow: 'hidden', p: 0 }
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
onEntered: () => {
|
||||||
|
inputRef.current?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TimezoneAutocomplete
|
||||||
|
size="medium"
|
||||||
|
value={effectiveTimezone}
|
||||||
|
onChange={onChange}
|
||||||
|
zones={timezoneList.zones}
|
||||||
|
getTimezoneOffset={(tzName: string) =>
|
||||||
|
getTimezoneOffset(tzName, referenceDate)
|
||||||
|
}
|
||||||
|
inputRef={inputRef}
|
||||||
|
openOnFocus
|
||||||
|
showIcon={false}
|
||||||
|
inputFontSize="14px"
|
||||||
|
inputPadding="2px 4px"
|
||||||
|
onClose={onClose}
|
||||||
|
disableClearable={true}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { getTimezoneOffset, resolveTimezone } from '@/utils/timezone'
|
||||||
|
import {
|
||||||
|
SwipeableDrawer,
|
||||||
|
Box,
|
||||||
|
TextField,
|
||||||
|
List,
|
||||||
|
InputAdornment
|
||||||
|
} from '@linagora/twake-mui'
|
||||||
|
import { Search as SearchIcon } from '@mui/icons-material'
|
||||||
|
import React, { useState, useMemo, useEffect, useRef } from 'react'
|
||||||
|
import { useTimeZoneList } from './hooks/useTimeZoneList'
|
||||||
|
import { TimezoneSelectProps } from './TimezoneSelector'
|
||||||
|
import { useI18n } from 'twake-i18n'
|
||||||
|
import { TimezoneListItem } from './TimezoneListItem'
|
||||||
|
|
||||||
|
const filterTimezones = (
|
||||||
|
zones: string[],
|
||||||
|
query: string,
|
||||||
|
referenceDate: Date
|
||||||
|
): string[] => {
|
||||||
|
if (!query) return zones
|
||||||
|
return zones.filter(tz => {
|
||||||
|
const label = tz.replace(/_/g, ' ').toLowerCase()
|
||||||
|
const offset = getTimezoneOffset(tz, referenceDate).toLowerCase()
|
||||||
|
return label.includes(query) || offset.includes(query)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SmallTimezoneSelector: React.FC<
|
||||||
|
TimezoneSelectProps & {
|
||||||
|
onClose: () => void
|
||||||
|
open: boolean
|
||||||
|
}
|
||||||
|
> = ({ value, onChange, referenceDate, onClose, open }) => {
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const timezoneList = useTimeZoneList()
|
||||||
|
|
||||||
|
const effectiveTimezone = value
|
||||||
|
? resolveTimezone(value)
|
||||||
|
: timezoneList.browserTz
|
||||||
|
|
||||||
|
const filteredTimeZones = useMemo(() => {
|
||||||
|
const query = searchQuery.toLowerCase().trim()
|
||||||
|
return filterTimezones(timezoneList.zones, query, referenceDate)
|
||||||
|
}, [timezoneList.zones, searchQuery, referenceDate])
|
||||||
|
|
||||||
|
const handleSelect = (tz: string): void => {
|
||||||
|
onChange(tz)
|
||||||
|
setSearchQuery('')
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedRef = useRef<HTMLDivElement>(null)
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
useEffect((): void => {
|
||||||
|
if (open) {
|
||||||
|
inputRef.current?.focus()
|
||||||
|
selectedRef.current?.scrollIntoView({
|
||||||
|
behavior: 'auto',
|
||||||
|
block: 'center'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SwipeableDrawer
|
||||||
|
anchor="bottom"
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
onOpen={(): void => {}}
|
||||||
|
disableAutoFocus
|
||||||
|
slotProps={{
|
||||||
|
paper: {
|
||||||
|
sx: { height: '90%' }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ px: 2 }}>
|
||||||
|
<TextField
|
||||||
|
inputRef={inputRef}
|
||||||
|
autoFocus
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
placeholder={t('calendar.searchTimezone')}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(
|
||||||
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||||
|
) => setSearchQuery(e.target.value)}
|
||||||
|
slotProps={{
|
||||||
|
htmlInput: {
|
||||||
|
'aria-label': t('calendar.searchTimezone')
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon sx={{ color: 'action.active', mr: 1 }} />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
disableUnderline: true
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
'& .MuiInputBase-root': {
|
||||||
|
padding: '8px 0'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<List sx={{ overflow: 'auto', flex: 1, pt: 0 }}>
|
||||||
|
{filteredTimeZones.map(tz => (
|
||||||
|
<TimezoneListItem
|
||||||
|
key={tz}
|
||||||
|
tz={tz}
|
||||||
|
referenceDate={referenceDate}
|
||||||
|
isSelected={effectiveTimezone === tz}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
selectedRef={selectedRef}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</SwipeableDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { getTimezoneOffset } from '@/utils/timezone'
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemText,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@linagora/twake-mui'
|
||||||
|
import { Check as CheckIcon } from '@mui/icons-material'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
interface TimezoneListItemProps {
|
||||||
|
tz: string
|
||||||
|
referenceDate: Date
|
||||||
|
isSelected: boolean
|
||||||
|
onSelect: (tz: string) => void
|
||||||
|
selectedRef: React.RefObject<HTMLDivElement> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimezoneListItem: React.FC<TimezoneListItemProps> = ({
|
||||||
|
tz,
|
||||||
|
referenceDate,
|
||||||
|
isSelected,
|
||||||
|
onSelect,
|
||||||
|
selectedRef
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const offset = getTimezoneOffset(tz, referenceDate)
|
||||||
|
const label = tz.split('/').pop()?.replace(/_/g, ' ') || tz
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
selected={isSelected}
|
||||||
|
aria-selected={isSelected}
|
||||||
|
ref={isSelected ? selectedRef : null}
|
||||||
|
onClick={() => onSelect(tz)}
|
||||||
|
sx={{ py: 1 }}
|
||||||
|
>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Typography>
|
||||||
|
({offset}) {label}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{isSelected && (
|
||||||
|
<CheckIcon
|
||||||
|
sx={{ color: theme.palette.primary.main, fontSize: '20px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { getTimezoneOffset, resolveTimezone } from '@/utils/timezone'
|
||||||
|
import { Button } from '@linagora/twake-mui'
|
||||||
|
import { MouseEvent, useState } from 'react'
|
||||||
|
import { useI18n } from 'twake-i18n'
|
||||||
|
import { useTimeZoneList } from './hooks/useTimeZoneList'
|
||||||
|
import { LargeTimezoneSelector } from './LargeTimeZoneSelector'
|
||||||
|
import { useScreenSizeDetection } from '@/useScreenSizeDetection'
|
||||||
|
import { SmallTimezoneSelector } from './SmallTimeZoneSelector'
|
||||||
|
|
||||||
|
export interface TimezoneSelectProps {
|
||||||
|
value: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
referenceDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimezoneSelector: React.FC<TimezoneSelectProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
referenceDate
|
||||||
|
}) => {
|
||||||
|
const { isTooSmall: isMobile } = useScreenSizeDetection()
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const timezoneList = useTimeZoneList()
|
||||||
|
|
||||||
|
const effectiveTimezone = value
|
||||||
|
? resolveTimezone(value)
|
||||||
|
: timezoneList.browserTz
|
||||||
|
const safeTimezone = timezoneList.zones.includes(effectiveTimezone)
|
||||||
|
? effectiveTimezone
|
||||||
|
: timezoneList.browserTz
|
||||||
|
const selectedOffset = getTimezoneOffset(safeTimezone, referenceDate)
|
||||||
|
|
||||||
|
const handleOpen = (event: MouseEvent<HTMLElement>): void => {
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = (): void => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = Boolean(anchorEl)
|
||||||
|
const { t } = useI18n()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
onClick={handleOpen}
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: 'auto',
|
||||||
|
padding: '2px 4px',
|
||||||
|
margin: 0,
|
||||||
|
lineHeight: 1.2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedOffset || t('common.select_timezone')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isMobile ? (
|
||||||
|
<SmallTimezoneSelector
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
referenceDate={referenceDate}
|
||||||
|
onClose={handleClose}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LargeTimezoneSelector
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
referenceDate={referenceDate}
|
||||||
|
onClose={handleClose}
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
browserDefaultTimeZone,
|
||||||
|
getTimezoneOffset,
|
||||||
|
resolveTimezone
|
||||||
|
} from '@/utils/timezone'
|
||||||
|
import { TIMEZONES } from '@/utils/timezone-data'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
export function useTimeZoneList(): {
|
||||||
|
zones: string[]
|
||||||
|
browserTz: string
|
||||||
|
getTimezoneOffset: (tzName: string, date?: Date) => string
|
||||||
|
} {
|
||||||
|
return useMemo(() => {
|
||||||
|
const zones = Object.keys(TIMEZONES.zones).sort()
|
||||||
|
const browserTz = resolveTimezone(browserDefaultTimeZone)
|
||||||
|
|
||||||
|
return { zones, browserTz, getTimezoneOffset }
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
+9
-3
@@ -1,11 +1,17 @@
|
|||||||
|
declare module '*.styl'
|
||||||
|
|
||||||
declare module '@linagora/twake-mui' {
|
declare module '@linagora/twake-mui' {
|
||||||
export * from '@mui/material'
|
export * from '@mui/material'
|
||||||
import type { AvatarProps as MuiAvatarProps } from '@mui/material'
|
|
||||||
|
export function useTheme<T = import('@mui/material').Theme>(): T
|
||||||
|
|
||||||
export type AvatarSize = 'xs' | 's' | 'm' | 'l' | 'xl'
|
export type AvatarSize = 'xs' | 's' | 'm' | 'l' | 'xl'
|
||||||
export type AvatarDisplay = 'initial' | 'inline'
|
export type AvatarDisplay = 'initial' | 'inline'
|
||||||
|
|
||||||
export interface AvatarProps extends Omit<MuiAvatarProps, 'color'> {
|
export interface AvatarProps extends Omit<
|
||||||
|
import('@mui/material').AvatarProps,
|
||||||
|
'color'
|
||||||
|
> {
|
||||||
color?: string
|
color?: string
|
||||||
size?: AvatarSize | number
|
size?: AvatarSize | number
|
||||||
border?: boolean
|
border?: boolean
|
||||||
@@ -14,7 +20,7 @@ declare module '@linagora/twake-mui' {
|
|||||||
display?: AvatarDisplay
|
display?: AvatarDisplay
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Avatar: React.FC<AvatarProps>
|
export const Avatar: import('react').FC<AvatarProps>
|
||||||
|
|
||||||
export const radius: Record<string, string | number>
|
export const radius: Record<string, string | number>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
import { useAppDispatch, useAppSelector } from '@/app/hooks'
|
||||||
import { useTimeZoneList } from '@/components/Calendar/TimezoneSelector'
|
import { useTimeZoneList } from '@/components/Timezone/hooks/useTimeZoneList'
|
||||||
import { WeekDaySelector } from '@/components/Event/WeekDaySelector'
|
import { WeekDaySelector } from '@/components/Event/WeekDaySelector'
|
||||||
import { TimezoneAutocomplete } from '@/components/Timezone/TimezoneAutocomplete'
|
import { TimezoneAutocomplete } from '@/components/Timezone/TimezoneAutocomplete'
|
||||||
import { browserDefaultTimeZone, getTimezoneOffset } from '@/utils/timezone'
|
import { browserDefaultTimeZone, getTimezoneOffset } from '@/utils/timezone'
|
||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
SelectChangeEvent,
|
|
||||||
Switch,
|
Switch,
|
||||||
Typography
|
Typography
|
||||||
} from '@linagora/twake-mui'
|
} from '@linagora/twake-mui'
|
||||||
@@ -40,13 +39,13 @@ interface GeneralSettingsProps {
|
|||||||
onWorkingDaysError: () => void
|
onWorkingDaysError: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GeneralSettings({
|
export const GeneralSettings: React.FC<GeneralSettingsProps> = ({
|
||||||
onLanguageError,
|
onLanguageError,
|
||||||
onTimeZoneError,
|
onTimeZoneError,
|
||||||
onHideDeclinedEventsError,
|
onHideDeclinedEventsError,
|
||||||
onDisplayWeekNumbersError,
|
onDisplayWeekNumbersError,
|
||||||
onWorkingDaysError
|
onWorkingDaysError
|
||||||
}: GeneralSettingsProps) {
|
}) => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -79,7 +78,20 @@ export function GeneralSettings({
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleLanguageChange = (event: SelectChangeEvent<string>) => {
|
const handleLanguageChange = (
|
||||||
|
event:
|
||||||
|
| React.ChangeEvent<
|
||||||
|
Omit<HTMLInputElement, 'value'> & {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
| (Event & {
|
||||||
|
target: {
|
||||||
|
value: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
): void => {
|
||||||
const newLanguage = event.target.value
|
const newLanguage = event.target.value
|
||||||
const previousLanguage = currentLanguage
|
const previousLanguage = currentLanguage
|
||||||
dispatch(setUserLanguage(newLanguage))
|
dispatch(setUserLanguage(newLanguage))
|
||||||
@@ -93,7 +105,7 @@ export function GeneralSettings({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTimeZoneChange = (newTimeZone: string) => {
|
const handleTimeZoneChange = (newTimeZone: string): void => {
|
||||||
const previousTimeZone = currentTimeZone
|
const previousTimeZone = currentTimeZone
|
||||||
dispatch(setUserTimeZone(newTimeZone))
|
dispatch(setUserTimeZone(newTimeZone))
|
||||||
dispatch(setSettingsTimeZone(newTimeZone))
|
dispatch(setSettingsTimeZone(newTimeZone))
|
||||||
@@ -108,7 +120,7 @@ export function GeneralSettings({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTimeZoneDefaultChange = (isDefault: boolean) => {
|
const handleTimeZoneDefaultChange = (isDefault: boolean): void => {
|
||||||
const previousTimeZone = currentTimeZone
|
const previousTimeZone = currentTimeZone
|
||||||
dispatch(setIsBrowserDefaultTimeZone(isDefault))
|
dispatch(setIsBrowserDefaultTimeZone(isDefault))
|
||||||
if (isDefault) {
|
if (isDefault) {
|
||||||
@@ -127,7 +139,7 @@ export function GeneralSettings({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHideDeclinedEvents = (value: boolean) => {
|
const handleHideDeclinedEvents = (value: boolean): void => {
|
||||||
dispatch(setHideDeclinedEvents(value))
|
dispatch(setHideDeclinedEvents(value))
|
||||||
dispatch(updateUserConfigurationsAsync({ hideDeclinedEvents: value }))
|
dispatch(updateUserConfigurationsAsync({ hideDeclinedEvents: value }))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -137,7 +149,7 @@ export function GeneralSettings({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDisplayWeekNumbers = (value: boolean) => {
|
const handleDisplayWeekNumbers = (value: boolean): void => {
|
||||||
dispatch(setDisplayWeekNumbers(value))
|
dispatch(setDisplayWeekNumbers(value))
|
||||||
dispatch(updateUserConfigurationsAsync({ displayWeekNumbers: value }))
|
dispatch(updateUserConfigurationsAsync({ displayWeekNumbers: value }))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -178,14 +190,14 @@ export function GeneralSettings({
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return (): void => {
|
||||||
if (businessHoursTimeoutRef.current) {
|
if (businessHoursTimeoutRef.current) {
|
||||||
clearTimeout(businessHoursTimeoutRef.current)
|
clearTimeout(businessHoursTimeoutRef.current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleWorkingDays = (value: boolean) => {
|
const handleWorkingDays = (value: boolean): void => {
|
||||||
dispatch(setWorkingDays(value))
|
dispatch(setWorkingDays(value))
|
||||||
dispatch(updateUserConfigurationsAsync({ workingDays: value }))
|
dispatch(updateUserConfigurationsAsync({ workingDays: value }))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|||||||
+2
-1
@@ -27,7 +27,8 @@
|
|||||||
"sharedWarning": "You will lose access to its events. You will still be able to add it back later."
|
"sharedWarning": "You will lose access to its events. You will still be able to add it back later."
|
||||||
},
|
},
|
||||||
"resources": "Resources",
|
"resources": "Resources",
|
||||||
"browseResources": "Browse resources"
|
"browseResources": "Browse resources",
|
||||||
|
"searchTimezone": "Search timezone..."
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"modify": "Modify",
|
"modify": "Modify",
|
||||||
|
|||||||
+2
-1
@@ -27,7 +27,8 @@
|
|||||||
"sharedWarning": "Vous allez perdre l’accès à ses événements. Vous pourrez toujours le rajouter plus tard."
|
"sharedWarning": "Vous allez perdre l’accès à ses événements. Vous pourrez toujours le rajouter plus tard."
|
||||||
},
|
},
|
||||||
"resources": "Ressources",
|
"resources": "Ressources",
|
||||||
"browseResources": "Parcourir les ressources"
|
"browseResources": "Parcourir les ressources",
|
||||||
|
"searchTimezone": "Rechercher un fuseau horaire..."
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"modify": "Modifier",
|
"modify": "Modifier",
|
||||||
|
|||||||
+2
-1
@@ -27,7 +27,8 @@
|
|||||||
"sharedWarning": "Вы потеряете доступ. Позже можно добавить снова."
|
"sharedWarning": "Вы потеряете доступ. Позже можно добавить снова."
|
||||||
},
|
},
|
||||||
"resources": "Ресурсы",
|
"resources": "Ресурсы",
|
||||||
"browseResources": "Просмотреть ресурсы"
|
"browseResources": "Просмотреть ресурсы",
|
||||||
|
"searchTimezone": "Поиск часового пояса..."
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"modify": "Изменить",
|
"modify": "Изменить",
|
||||||
|
|||||||
+2
-1
@@ -27,7 +27,8 @@
|
|||||||
"sharedWarning": "Bạn sẽ mất quyền truy cập các sự kiện của lịch. Bạn vẫn có thể thêm lại sau."
|
"sharedWarning": "Bạn sẽ mất quyền truy cập các sự kiện của lịch. Bạn vẫn có thể thêm lại sau."
|
||||||
},
|
},
|
||||||
"resources": "Tài nguyên",
|
"resources": "Tài nguyên",
|
||||||
"browseResources": "Tìm kiếm tài nguyên"
|
"browseResources": "Tìm kiếm tài nguyên",
|
||||||
|
"searchTimezone": "Tìm kiếm múi giờ..."
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"modify": "Chỉnh sửa",
|
"modify": "Chỉnh sửa",
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useMediaQuery, useTheme } from '@linagora/twake-mui'
|
import { useMediaQuery, useTheme } from '@linagora/twake-mui'
|
||||||
|
|
||||||
export function useScreenSizeDetection() {
|
export function useScreenSizeDetection(): {
|
||||||
|
isTooSmall: boolean
|
||||||
|
isTablet: boolean
|
||||||
|
} {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'))
|
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'))
|
||||||
|
|||||||
Reference in New Issue
Block a user