2
package-lock.json
generated
2
package-lock.json
generated
@@ -39,7 +39,7 @@
|
||||
"@jcesarmobile/capacitor-ocr": "^0.3.0",
|
||||
"@meauxt/react-swipeable-list": "^1.0.0",
|
||||
"@mui/icons-material": "^5.16.13",
|
||||
"@mui/joy": "^5.0.0-beta.20",
|
||||
"@mui/joy": "5.0.0-beta.52",
|
||||
"@mui/material": "^5.15.2",
|
||||
"@openreplay/tracker": "^14.0.4",
|
||||
"@revenuecat/purchases-capacitor": "^12.0.0",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"@jcesarmobile/capacitor-ocr": "^0.3.0",
|
||||
"@meauxt/react-swipeable-list": "^1.0.0",
|
||||
"@mui/icons-material": "^5.16.13",
|
||||
"@mui/joy": "^5.0.0-beta.20",
|
||||
"@mui/joy": "5.0.0-beta.52",
|
||||
"@mui/material": "^5.15.2",
|
||||
"@openreplay/tracker": "^14.0.4",
|
||||
"@revenuecat/purchases-capacitor": "^12.0.0",
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"filters": "Filters",
|
||||
"activities": "Activities",
|
||||
"points": "Points",
|
||||
"settings": "Settings"
|
||||
"settings": "Settings",
|
||||
"reportBug": "Report a Bug"
|
||||
},
|
||||
"search": {
|
||||
"title": "Search",
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Close } from '@mui/icons-material'
|
||||
import { Box, Divider, IconButton, Modal, Sheet, Typography } from '@mui/joy'
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
import { forwardRef, useId } from 'react'
|
||||
import { Z_INDEX } from '../../constants/zIndex'
|
||||
|
||||
const WIDTH_BY_SIZE = {
|
||||
sm: 400,
|
||||
@@ -27,6 +26,7 @@ const AppModal = forwardRef(
|
||||
size = 'md',
|
||||
fullWidth = true,
|
||||
isMobile: isMobileProp,
|
||||
keepMounted = false,
|
||||
mobilePresentation = 'sheet',
|
||||
role = 'dialog',
|
||||
showCloseButton = true,
|
||||
@@ -63,9 +63,16 @@ const AppModal = forwardRef(
|
||||
onClose={handleClose}
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={descriptionId}
|
||||
keepMounted
|
||||
keepMounted={keepMounted}
|
||||
sx={{
|
||||
zIndex: Z_INDEX.MODAL_BACKDROP,
|
||||
// Joy raises portaled listboxes above modals, but its selector misses
|
||||
// Menu because Menu renders with role="menu". Keep the workaround in
|
||||
// the modal primitive so individual menus never need a z-index.
|
||||
...(open && {
|
||||
'& ~ [role="menu"]': {
|
||||
'--unstable_popup-zIndex': 'calc(var(--joy-zIndex-modal) + 1)',
|
||||
},
|
||||
}),
|
||||
display: 'flex',
|
||||
alignItems: isSheet ? 'flex-end' : 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -85,7 +92,6 @@ const AppModal = forwardRef(
|
||||
aria-describedby={descriptionId}
|
||||
variant='outlined'
|
||||
sx={{
|
||||
zIndex: Z_INDEX.MODAL_CONTENT,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: isFullscreen
|
||||
|
||||
34
src/constants/settingsSections.js
Normal file
34
src/constants/settingsSections.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
AccountCircle,
|
||||
Api,
|
||||
Circle,
|
||||
Code,
|
||||
FamilyRestroom,
|
||||
Language,
|
||||
Notifications,
|
||||
Palette,
|
||||
Person,
|
||||
Security,
|
||||
Settings,
|
||||
Storage,
|
||||
ViewSidebar,
|
||||
} from '@mui/icons-material'
|
||||
|
||||
// Single source of truth for the settings sections: id, icon, and access
|
||||
// gating. Titles/descriptions live in locales/settings.json under
|
||||
// `overview.sections.<id>`, keyed off the same ids.
|
||||
export const SETTINGS_SECTIONS = [
|
||||
{ id: 'profile', icon: Person },
|
||||
{ id: 'circle', icon: Circle, parentOnly: true },
|
||||
{ id: 'account', icon: AccountCircle, parentOnly: true },
|
||||
{ id: 'subaccounts', icon: FamilyRestroom },
|
||||
{ id: 'notifications', icon: Notifications },
|
||||
{ id: 'mfa', icon: Security, parentOnly: true },
|
||||
{ id: 'apitokens', icon: Api, parentOnly: true },
|
||||
{ id: 'storage', icon: Storage },
|
||||
{ id: 'sidepanel', icon: ViewSidebar },
|
||||
{ id: 'theme', icon: Palette },
|
||||
{ id: 'localization', icon: Language, isBeta: true },
|
||||
{ id: 'advanced', icon: Settings },
|
||||
{ id: 'developer', icon: Code },
|
||||
]
|
||||
@@ -1,38 +0,0 @@
|
||||
// Z-index constants for consistent layering
|
||||
// Lower values appear behind higher values
|
||||
|
||||
export const Z_INDEX = {
|
||||
// Base layer (0-99)
|
||||
BASE: 0,
|
||||
CARD_OVERLAY: 1,
|
||||
DROPDOWN_ITEM: 2,
|
||||
TOOLTIP: 3,
|
||||
|
||||
// UI Components (100-999)
|
||||
SAFE_AREA: 100,
|
||||
CALENDAR: 110,
|
||||
SMART_INPUT: 110,
|
||||
AUTOCOMPLETE: 200,
|
||||
|
||||
// Navigation (1000-1999)
|
||||
NAVBAR: 1000,
|
||||
DRAWER: 999,
|
||||
|
||||
// Modals and Overlays (2000-8999)
|
||||
MODAL_BACKDROP: 2000,
|
||||
MODAL_CONTENT: 2001,
|
||||
MODAL_CLOSE_BUTTON: 2002,
|
||||
// Popups that must float above open modals (portaled to document.body)
|
||||
MODAL_POPOVER: 2100,
|
||||
TOAST: 3000,
|
||||
|
||||
// Critical System UI (9000-9999)
|
||||
LOADING_SCREEN: 9000,
|
||||
ALERTS: 9500,
|
||||
NETWORK_BANNER: 9600,
|
||||
|
||||
// Maximum (10000+) - Reserved for absolute emergencies
|
||||
EMERGENCY: 10000,
|
||||
}
|
||||
|
||||
export default Z_INDEX
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { offlineDB } from '../utils/OfflineDB'
|
||||
@@ -34,6 +35,7 @@ const uniqueBy = (items, getId) => [
|
||||
|
||||
export const GlobalSearchProvider = ({ children }) => {
|
||||
const queryClient = useQueryClient()
|
||||
const { t } = useTranslation('settings')
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const isMobile = useMediaQuery('(max-width:768px)')
|
||||
@@ -110,6 +112,7 @@ export const GlobalSearchProvider = ({ children }) => {
|
||||
labels,
|
||||
members,
|
||||
isParent: isParentUser(profile),
|
||||
t,
|
||||
choresById: new Map(chores.map(item => [String(item.id), item])),
|
||||
projectsById: new Map(projects.map(item => [String(item.id), item])),
|
||||
membersById: new Map(members.map(item => [String(item.userId), item])),
|
||||
@@ -127,7 +130,7 @@ export const GlobalSearchProvider = ({ children }) => {
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [queryClient])
|
||||
}, [queryClient, t])
|
||||
|
||||
const openSearch = useCallback(
|
||||
(query = '') => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { SETTINGS_SECTIONS } from '../constants/settingsSections'
|
||||
|
||||
const stripHtml = value => {
|
||||
if (!value) return ''
|
||||
if (typeof globalThis.document === 'undefined')
|
||||
@@ -17,26 +19,6 @@ const HISTORY_STATUS = {
|
||||
6: 'rescheduled',
|
||||
}
|
||||
|
||||
const SETTINGS = [
|
||||
['profile', 'Profile', 'Name, avatar and personal details'],
|
||||
['circle', 'Circle', 'Members and household settings', true],
|
||||
['account', 'Account', 'Subscription and account management', true],
|
||||
['subaccounts', 'Subaccounts', 'Manage child accounts'],
|
||||
['notifications', 'Notifications', 'Reminders and notification preferences'],
|
||||
['mfa', 'Multi-factor authentication', 'Secure your account', true],
|
||||
['apitokens', 'API tokens', 'Manage integrations and access tokens', true],
|
||||
['storage', 'Storage', 'Files, backups and device storage'],
|
||||
['sidepanel', 'Side panel', 'Customize navigation'],
|
||||
['theme', 'Appearance', 'Theme, dark mode and colors'],
|
||||
['localization', 'Language and region', 'Language, dates and time formats'],
|
||||
[
|
||||
'advanced',
|
||||
'Advanced settings',
|
||||
'Offline support, webhooks and application behavior',
|
||||
],
|
||||
['developer', 'Developer settings', 'Diagnostics and experimental tools'],
|
||||
]
|
||||
|
||||
const providers = []
|
||||
|
||||
export const registerSearchProvider = provider => {
|
||||
@@ -166,15 +148,15 @@ registerSearchProvider({
|
||||
|
||||
registerSearchProvider({
|
||||
id: 'settings',
|
||||
getDocuments: ({ isParent }) =>
|
||||
SETTINGS.filter(([, , , parentOnly]) => !parentOnly || isParent).map(
|
||||
([id, title, description]) =>
|
||||
getDocuments: ({ isParent, t }) =>
|
||||
SETTINGS_SECTIONS.filter(({ parentOnly }) => !parentOnly || isParent).map(
|
||||
({ id }) =>
|
||||
document('settings', {
|
||||
id: `setting:${id}`,
|
||||
entityId: id,
|
||||
title,
|
||||
title: t(`overview.sections.${id}.title`),
|
||||
subtitle: 'Settings',
|
||||
body: description,
|
||||
body: t(`overview.sections.${id}.description`),
|
||||
keywords: `preferences configuration ${id}`,
|
||||
route: `/settings/${id}`,
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Alert, Box } from '@mui/joy'
|
||||
import PropTypes from 'prop-types'
|
||||
import { createContext, useCallback, useContext, useState } from 'react'
|
||||
import Z_INDEX from '../constants/zIndex'
|
||||
|
||||
const FADE_DURATION = 400 // ms
|
||||
const ALERT_DURATION = 5000 // ms
|
||||
@@ -48,7 +47,7 @@ export const AlertsProvider = ({ children }) => {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
zIndex: Z_INDEX.ALERTS,
|
||||
zIndex: 'var(--joy-zIndex-snackbar)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -38,16 +38,17 @@ import {
|
||||
ButtonGroup,
|
||||
Chip,
|
||||
Divider,
|
||||
Dropdown,
|
||||
IconButton,
|
||||
Input,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import AppModal from '../../../components/common/AppModal'
|
||||
import ActiveFilterChips from '../../../components/common/filter/ActiveFilterChips'
|
||||
import { Z_INDEX } from '../../../constants/zIndex'
|
||||
import KeyboardShortcutHint from '../../../components/common/KeyboardShortcutHint'
|
||||
import { FILTER_COLORS } from '../../../utils/Colors'
|
||||
import Priorities from '../../../utils/Priorities'
|
||||
@@ -229,9 +230,8 @@ const ChoreToolbar = ({
|
||||
const [localSelections, setLocalSelections] = useState(defaultSelections())
|
||||
const [savingFilter, setSavingFilter] = useState(false)
|
||||
const [saveFilterName, setSaveFilterName] = useState('')
|
||||
const [saveMenuAnchorEl, setSaveMenuAnchorEl] = useState(null)
|
||||
const [saveMenuOpen, setSaveMenuOpen] = useState(false)
|
||||
const [editingSavedFilter, setEditingSavedFilter] = useState(null)
|
||||
const saveMenuRef = useRef(null)
|
||||
const activeConditions = selectionsToConditions(localSelections)
|
||||
|
||||
// ── badge counts ─────────────────────────────────────────────────────────────
|
||||
@@ -419,13 +419,13 @@ const ChoreToolbar = ({
|
||||
}
|
||||
setSavingFilter(false)
|
||||
setSaveFilterName('')
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
setFilterSheetOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!filterSheetOpen || savingFilter || activeConditions.length === 0) {
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
}
|
||||
}, [filterSheetOpen, savingFilter, activeConditions.length])
|
||||
|
||||
@@ -504,7 +504,7 @@ const ChoreToolbar = ({
|
||||
onFilterSaved?.(editingSavedFilter.name)
|
||||
})
|
||||
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
setFilterSheetOpen(false)
|
||||
}
|
||||
|
||||
@@ -681,7 +681,7 @@ const ChoreToolbar = ({
|
||||
open={filterSheetOpen}
|
||||
isMobile
|
||||
onClose={() => {
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
setFilterSheetOpen(false)
|
||||
}}
|
||||
maxHeight='92vh'
|
||||
@@ -747,7 +747,7 @@ const ChoreToolbar = ({
|
||||
disabled={!hasAnyActive && activeConditions.length === 0}
|
||||
onClick={() => {
|
||||
setLocalSelections(defaultSelections())
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
onClearAllFilters?.()
|
||||
setFilterSheetOpen(false)
|
||||
}}
|
||||
@@ -756,33 +756,29 @@ const ChoreToolbar = ({
|
||||
</Button>
|
||||
|
||||
{activeConditions.length > 0 ? (
|
||||
<>
|
||||
<Dropdown
|
||||
open={saveMenuOpen}
|
||||
onOpenChange={(_event, isOpen) => setSaveMenuOpen(isOpen)}
|
||||
>
|
||||
<ButtonGroup variant='solid' color='primary'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
setFilterSheetOpen(false)
|
||||
}}
|
||||
sx={{ minWidth: 140 }}
|
||||
>
|
||||
{resultCount != null ? `Show ${resultCount}` : 'Done'}
|
||||
</Button>
|
||||
<IconButton
|
||||
ref={saveMenuRef}
|
||||
<MenuButton
|
||||
slots={{ root: IconButton }}
|
||||
aria-label='More save options'
|
||||
onClick={e => setSaveMenuAnchorEl(e.currentTarget)}
|
||||
>
|
||||
<ArrowDropDown />
|
||||
</IconButton>
|
||||
</MenuButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<Menu
|
||||
anchorEl={saveMenuAnchorEl}
|
||||
open={Boolean(saveMenuAnchorEl)}
|
||||
onClose={() => setSaveMenuAnchorEl(null)}
|
||||
placement='top-end'
|
||||
sx={{ zIndex: Z_INDEX.MODAL_CONTENT + 10 }}
|
||||
>
|
||||
<Menu placement='top-end'>
|
||||
<MenuItem
|
||||
onClick={handleUpdateFilter}
|
||||
disabled={!editingSavedFilter}
|
||||
@@ -792,7 +788,7 @@ const ChoreToolbar = ({
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
setSaveFilterName(
|
||||
editingSavedFilter
|
||||
? `${editingSavedFilter.name} Copy`
|
||||
@@ -805,13 +801,13 @@ const ChoreToolbar = ({
|
||||
Save as New Filter
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
</Dropdown>
|
||||
) : (
|
||||
<Button
|
||||
variant='solid'
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
setSaveMenuAnchorEl(null)
|
||||
setSaveMenuOpen(false)
|
||||
setFilterSheetOpen(false)
|
||||
}}
|
||||
sx={{ minWidth: 140 }}
|
||||
|
||||
@@ -1,22 +1,4 @@
|
||||
import {
|
||||
AccountCircle,
|
||||
Api,
|
||||
BugReport,
|
||||
ChevronRight,
|
||||
Circle,
|
||||
Code,
|
||||
FamilyRestroom,
|
||||
Feedback,
|
||||
Language,
|
||||
Notifications,
|
||||
Palette,
|
||||
Person,
|
||||
Security,
|
||||
Settings,
|
||||
Star,
|
||||
Storage,
|
||||
ViewSidebar,
|
||||
} from '@mui/icons-material'
|
||||
import { BugReport, ChevronRight, Feedback, Star } from '@mui/icons-material'
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
@@ -37,6 +19,7 @@ import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { SETTINGS_SECTIONS } from '../../constants/settingsSections'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import { isParentUser } from '../../utils/UserHelpers'
|
||||
@@ -51,85 +34,13 @@ const SettingsOverview = () => {
|
||||
const [bugReportOpen, setBugReportOpen] = useState(false)
|
||||
|
||||
const settingsCards = [
|
||||
{
|
||||
id: 'profile',
|
||||
title: t('overview.sections.profile.title'),
|
||||
description: t('overview.sections.profile.description'),
|
||||
icon: <Person />,
|
||||
},
|
||||
{
|
||||
id: 'circle',
|
||||
title: t('overview.sections.circle.title'),
|
||||
description: t('overview.sections.circle.description'),
|
||||
icon: <Circle />,
|
||||
},
|
||||
{
|
||||
id: 'account',
|
||||
title: t('overview.sections.account.title'),
|
||||
description: t('overview.sections.account.description'),
|
||||
icon: <AccountCircle />,
|
||||
},
|
||||
{
|
||||
id: 'subaccounts',
|
||||
title: t('overview.sections.subaccounts.title'),
|
||||
description: t('overview.sections.subaccounts.description'),
|
||||
icon: <FamilyRestroom />,
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
title: t('overview.sections.notifications.title'),
|
||||
description: t('overview.sections.notifications.description'),
|
||||
icon: <Notifications />,
|
||||
},
|
||||
{
|
||||
id: 'mfa',
|
||||
title: t('overview.sections.mfa.title'),
|
||||
description: t('overview.sections.mfa.description'),
|
||||
icon: <Security />,
|
||||
},
|
||||
{
|
||||
id: 'apitokens',
|
||||
title: t('overview.sections.apitokens.title'),
|
||||
description: t('overview.sections.apitokens.description'),
|
||||
icon: <Api />,
|
||||
},
|
||||
{
|
||||
id: 'storage',
|
||||
title: t('overview.sections.storage.title'),
|
||||
description: t('overview.sections.storage.description'),
|
||||
icon: <Storage />,
|
||||
},
|
||||
{
|
||||
id: 'sidepanel',
|
||||
title: t('overview.sections.sidepanel.title'),
|
||||
description: t('overview.sections.sidepanel.description'),
|
||||
icon: <ViewSidebar />,
|
||||
},
|
||||
{
|
||||
id: 'theme',
|
||||
title: t('overview.sections.theme.title'),
|
||||
description: t('overview.sections.theme.description'),
|
||||
icon: <Palette />,
|
||||
},
|
||||
{
|
||||
id: 'localization',
|
||||
title: t('overview.sections.localization.title'),
|
||||
description: t('overview.sections.localization.description'),
|
||||
icon: <Language />,
|
||||
isBeta: true,
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
title: t('overview.sections.advanced.title'),
|
||||
description: t('overview.sections.advanced.description'),
|
||||
icon: <Settings />,
|
||||
},
|
||||
{
|
||||
id: 'developer',
|
||||
title: t('overview.sections.developer.title'),
|
||||
description: t('overview.sections.developer.description'),
|
||||
icon: <Code />,
|
||||
},
|
||||
...SETTINGS_SECTIONS.map(({ icon: Icon, id, isBeta }) => ({
|
||||
id,
|
||||
title: t(`overview.sections.${id}.title`),
|
||||
description: t(`overview.sections.${id}.description`),
|
||||
icon: <Icon />,
|
||||
isBeta,
|
||||
})),
|
||||
{
|
||||
id: 'feedback',
|
||||
title: t('overview.sections.feedback.title'),
|
||||
@@ -154,23 +65,19 @@ const SettingsOverview = () => {
|
||||
navigate(`/settings/${setting.id}`)
|
||||
}
|
||||
|
||||
const parentOnlyIds = SETTINGS_SECTIONS.filter(
|
||||
section => section.parentOnly,
|
||||
).map(section => section.id)
|
||||
|
||||
// Filter settings based on user type
|
||||
const getAvailableSettings = () => {
|
||||
const parentOnlySettings = [
|
||||
'children',
|
||||
'mfa',
|
||||
'apitokens',
|
||||
'circle',
|
||||
'account',
|
||||
]
|
||||
|
||||
if (isParentUser(userProfile)) {
|
||||
// Parent users can access all settings
|
||||
return settingsCards
|
||||
} else {
|
||||
// Child users can only access basic settings
|
||||
return settingsCards.filter(
|
||||
setting => !parentOnlySettings.includes(setting.id),
|
||||
setting => !parentOnlyIds.includes(setting.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { Add } from '@mui/icons-material'
|
||||
import { Divider, Menu, MenuItem } from '@mui/joy'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import { Z_INDEX } from '../../constants/zIndex'
|
||||
|
||||
const AutocompleteDropdown = ({
|
||||
currentValue,
|
||||
onCreateSuggestion, // Called when the "Create new" row is chosen
|
||||
@@ -62,7 +60,6 @@ const AutocompleteDropdown = ({
|
||||
position: 'relative',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
zIndex: Z_INDEX.MODAL_POPOVER,
|
||||
}}
|
||||
>
|
||||
{filteredOptions.map((option, index) => (
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { Add } from '@mui/icons-material'
|
||||
import { Box, Button, Typography } from '@mui/joy'
|
||||
import { Add, KeyboardArrowDown } from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dropdown,
|
||||
ListItemDecorator,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useMediaQuery } from '@mui/material'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import * as chrono from 'chrono-node'
|
||||
@@ -20,6 +29,7 @@ import LABEL_COLORS, { TASK_COLOR } from '../../utils/Colors'
|
||||
import { CreateLabel } from '../../utils/Fetcher'
|
||||
import { imageSourceToFile } from '../../utils/FileConvert'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import { getIconComponent } from '../../utils/ProjectIcons'
|
||||
import { generateUUID } from '../../utils/UUID'
|
||||
import { useLabels } from '../Labels/LabelQueries'
|
||||
import { useProjects } from '../Projects/ProjectQueries'
|
||||
@@ -122,6 +132,13 @@ const getInitialProject = () => {
|
||||
return 'default'
|
||||
}
|
||||
|
||||
const DEFAULT_PROJECT = {
|
||||
id: 'default',
|
||||
name: 'Default Project',
|
||||
color: '#9CA3AF',
|
||||
icon: 'FolderOpen',
|
||||
}
|
||||
|
||||
const PRIORITY_COLORS = {
|
||||
0: TASK_COLOR.NO_PRIORITY,
|
||||
1: TASK_COLOR.PRIORITY_1,
|
||||
@@ -175,7 +192,7 @@ const TaskInput = ({ initialMode, isModalOpen, onChoreUpdate, onClose }) => {
|
||||
const { data: userLabels, isLoading: userLabelsLoading } = useLabels()
|
||||
const { data: circleMembers, isLoading: isCircleMembersLoading } =
|
||||
useCircleMembers()
|
||||
const { isLoading: isProjectsLoading } = useProjects()
|
||||
const { data: projects, isLoading: isProjectsLoading } = useProjects()
|
||||
const createChoreMutation = useCreateChore()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -293,6 +310,17 @@ const TaskInput = ({ initialMode, isModalOpen, onChoreUpdate, onClose }) => {
|
||||
const [useCustomTime, setUseCustomTime] = useState(false)
|
||||
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false)
|
||||
const [projectId, setProjectId] = useState(getInitialProject)
|
||||
const selectedProject = useMemo(
|
||||
() =>
|
||||
(projectId !== 'default' &&
|
||||
projects?.find(project => project.id === projectId)) ||
|
||||
DEFAULT_PROJECT,
|
||||
[projects, projectId],
|
||||
)
|
||||
const SelectedProjectIcon = useMemo(
|
||||
() => getIconComponent(selectedProject.icon),
|
||||
[selectedProject],
|
||||
)
|
||||
const [attachments, setAttachments] = useState([])
|
||||
|
||||
const [draftId, setDraftId] = useState(() => generateUUID())
|
||||
@@ -1121,6 +1149,50 @@ const TaskInput = ({ initialMode, isModalOpen, onChoreUpdate, onClose }) => {
|
||||
title='Create new task'
|
||||
footer={
|
||||
<ModalActions>
|
||||
{!showScan && !showVoice && projects?.length >= 1 && (
|
||||
<Dropdown>
|
||||
<MenuButton
|
||||
variant='plain'
|
||||
color='neutral'
|
||||
size='sm'
|
||||
startDecorator={
|
||||
<SelectedProjectIcon
|
||||
sx={{ fontSize: 18, color: selectedProject.color }}
|
||||
/>
|
||||
}
|
||||
endDecorator={<KeyboardArrowDown sx={{ fontSize: 16 }} />}
|
||||
sx={{
|
||||
mr: 'auto',
|
||||
color: 'text.secondary',
|
||||
fontWeight: 'normal',
|
||||
}}
|
||||
>
|
||||
{selectedProject.name}
|
||||
</MenuButton>
|
||||
<Menu
|
||||
placement='top-start'
|
||||
sx={{ minWidth: 200, zIndex: Z_INDEX.MODAL_POPOVER }}
|
||||
>
|
||||
{[DEFAULT_PROJECT, ...projects].map(project => {
|
||||
const ProjectIcon = getIconComponent(project.icon)
|
||||
return (
|
||||
<MenuItem
|
||||
key={project.id}
|
||||
selected={projectId === project.id}
|
||||
onClick={() => setProjectId(project.id)}
|
||||
>
|
||||
<ListItemDecorator>
|
||||
<ProjectIcon
|
||||
sx={{ fontSize: 18, color: project.color }}
|
||||
/>
|
||||
</ListItemDecorator>
|
||||
{project.name}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
)}
|
||||
<Button
|
||||
variant='outlined'
|
||||
color='neutral'
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
import { ClickAwayListener, Popper } from '@mui/material'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Z_INDEX } from '../../constants/zIndex'
|
||||
import { useDocumentScanner } from '../../hooks/useDocumentScanner'
|
||||
import { useFileUpload } from '../../hooks/useFileUpload'
|
||||
import { useNotification } from '../../service/NotificationProvider'
|
||||
@@ -223,7 +222,7 @@ const AttachmentPickerField = ({
|
||||
options: { fallbackPlacements: ['bottom-start', 'top-start'] },
|
||||
},
|
||||
]}
|
||||
sx={{ zIndex: Z_INDEX.MODAL_CLOSE_BUTTON + 1 }}
|
||||
sx={{ zIndex: 'calc(var(--joy-zIndex-modal) + 1)' }}
|
||||
>
|
||||
<ClickAwayListener onClickAway={() => setIsOpen(false)}>
|
||||
<Sheet
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Close } from '@mui/icons-material'
|
||||
import { Box, Button, IconButton, Sheet, Typography } from '@mui/joy'
|
||||
import { ClickAwayListener, Popper } from '@mui/material'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Z_INDEX } from '../../constants/zIndex'
|
||||
|
||||
const BaseOptionPicker = ({
|
||||
items = [],
|
||||
@@ -199,7 +198,7 @@ const BaseOptionPicker = ({
|
||||
},
|
||||
},
|
||||
]}
|
||||
sx={{ zIndex: Z_INDEX.MODAL_CLOSE_BUTTON + 1 }}
|
||||
sx={{ zIndex: 'calc(var(--joy-zIndex-modal) + 1)' }}
|
||||
>
|
||||
<ClickAwayListener onClickAway={() => setIsOpen(false)}>
|
||||
<Sheet
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Capacitor } from '@capacitor/core'
|
||||
import {
|
||||
Archive,
|
||||
ArrowBack,
|
||||
BugReport,
|
||||
FilterAlt,
|
||||
FolderOpen,
|
||||
History,
|
||||
@@ -30,10 +31,11 @@ import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
|
||||
import { version } from '../../../package.json'
|
||||
import UserProfileAvatar from '../../components/UserProfileAvatar'
|
||||
import Z_INDEX from '../../constants/zIndex'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useResource } from '../../queries/ResourceQueries'
|
||||
import { useGlobalSearch } from '../../search/GlobalSearchContext'
|
||||
import { apiClient } from '../../utils/ApiClient'
|
||||
import ErrorReportModal from '../Modals/ErrorReportModal'
|
||||
import NavBarLink from './NavBarLink'
|
||||
import SyncStatusIndicator from './SyncStatusIndicator'
|
||||
|
||||
@@ -45,6 +47,7 @@ const NavBar = () => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [drawerOpen, setDrawerOpen] = useState(false)
|
||||
const [bugReportOpen, setBugReportOpen] = useState(false)
|
||||
|
||||
const links = [
|
||||
{
|
||||
@@ -206,7 +209,7 @@ const NavBar = () => {
|
||||
? `calc(var(--safe-area-inset-top, 0px))`
|
||||
: '',
|
||||
position: 'sticky',
|
||||
zIndex: Z_INDEX.NAVBAR,
|
||||
zIndex: 'var(--joy-zIndex-popup)',
|
||||
top: 0,
|
||||
minHeight: '35px',
|
||||
backgroundColor: 'var(--joy-palette-background-body)',
|
||||
@@ -239,7 +242,6 @@ const NavBar = () => {
|
||||
// height:
|
||||
// 'calc(100vh - var(--safe-area-inset-top, 0px) - var(--safe-area-inset-bottom, 0px))',
|
||||
overflow: 'auto',
|
||||
zIndex: Z_INDEX.DRAWER,
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -296,6 +298,17 @@ const NavBar = () => {
|
||||
</ListItemDecorator>
|
||||
<ListItemContent>Upgrade to Plus</ListItemContent>
|
||||
</ListItemButton> */}
|
||||
<ListItemButton
|
||||
onClick={() => setBugReportOpen(true)}
|
||||
sx={{
|
||||
py: 1.2,
|
||||
}}
|
||||
>
|
||||
<ListItemDecorator>
|
||||
<BugReport />
|
||||
</ListItemDecorator>
|
||||
<ListItemContent>{t('navigation.reportBug')}</ListItemContent>
|
||||
</ListItemButton>
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
apiClient.handleLogout()
|
||||
@@ -329,6 +342,10 @@ const NavBar = () => {
|
||||
</List>
|
||||
</div>
|
||||
</Drawer>
|
||||
<ErrorReportModal
|
||||
open={bugReportOpen}
|
||||
onClose={() => setBugReportOpen(false)}
|
||||
/>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WifiOff } from '@mui/icons-material'
|
||||
import { Alert, Box } from '@mui/joy'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Z_INDEX from '../../constants/zIndex'
|
||||
|
||||
import { networkManager } from '../../hooks/NetworkManager'
|
||||
|
||||
const NetworkBanner = () => {
|
||||
@@ -56,7 +56,7 @@ const NetworkBanner = () => {
|
||||
paddingTop: `calc(var(--safe-area-inset-top, 0px))`,
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: Z_INDEX.NETWORK_BANNER,
|
||||
zIndex: 'var(--joy-zIndex-snackbar)',
|
||||
pt: `calc( env(safe-area-inset-top, 0px))`,
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
|
||||
Reference in New Issue
Block a user