diff --git a/src/App.jsx b/src/App.jsx index 6aa369c..cbad980 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,12 +7,10 @@ import { Outlet, useNavigate } from 'react-router-dom' import { useRegisterSW } from 'virtual:pwa-register/react' import { registerCapacitorListeners } from './CapacitorListener' import { ImpersonateUserProvider } from './contexts/ImpersonateUserContext' -import { UserContext } from './contexts/UserContext' import { useResource } from './queries/ResourceQueries' import { AuthenticationProvider } from './service/AuthenticationService' import { ErrorProvider } from './service/ErrorProvider' -import { GetUserProfile } from './utils/Fetcher' -import { apiManager, isTokenValid } from './utils/TokenManager' +import { apiManager } from './utils/TokenManager' import NetworkBanner from './views/components/NetworkBanner' const add = className => { document.getElementById('root').classList.add(className) @@ -23,15 +21,14 @@ const remove = className => { } // TODO: Update the interval to at 60 minutes const intervalMS = 5 * 60 * 1000 // 5 minutes - +const queryClient = new QueryClient({}) function App() { const resource = useResource() const navigate = useNavigate() startApiManager(navigate) startOpenReplay() - const queryClient = new QueryClient() + const { mode, systemMode } = useColorScheme() - const [userProfile, setUserProfile] = useState(null) const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(true) const { @@ -72,23 +69,12 @@ function App() { return remove('dark') } - const getUserProfile = () => { - GetUserProfile() - .then(res => { - res.json().then(data => { - setUserProfile(data.res) - }) - }) - .catch(error => {}) - } + useEffect(() => { setThemeClass() }, [mode, systemMode]) useEffect(() => { registerCapacitorListeners() - if (isTokenValid()) { - if (!userProfile) getUserProfile() - } }, []) return ( @@ -99,10 +85,8 @@ function App() { - - - - + + @@ -133,6 +117,7 @@ const startOpenReplay = () => { const tracker = new Tracker({ projectKey: import.meta.env.VITE_OPENREPLAY_PROJECT_KEY, }) + tracker.start() } export default App diff --git a/src/components/NotificationTemplate.jsx b/src/components/NotificationTemplate.jsx index 9970ea9..bb02182 100644 --- a/src/components/NotificationTemplate.jsx +++ b/src/components/NotificationTemplate.jsx @@ -1,3 +1,4 @@ +import { Save } from '@mui/icons-material' import AddIcon from '@mui/icons-material/Add' import DeleteIcon from '@mui/icons-material/Delete' import InfoIcon from '@mui/icons-material/Info' @@ -14,66 +15,64 @@ import Typography from '@mui/joy/Typography' import { useCallback, useEffect, useState } from 'react' const timeUnits = [ - { label: 'Minutes', value: 'minutes' }, + { label: 'Mins', value: 'minutes' }, { label: 'Hours', value: 'hours' }, { label: 'Days', value: 'days' }, ] const beforeAfterOptions = [ - { label: 'Before Due', value: 'before' }, + { label: 'Before', value: 'before' }, { label: 'On Due', value: 'ondue' }, - { label: 'After Due', value: 'after' }, + { label: 'After', value: 'after' }, ] function getRelativeLabel(notification) { - const { amount, unit, when } = notification - - // For "On Due" notification - if (when === 'ondue') { + const { value, unit, type } = notification + if (type === 'ondue') { return 'On due date' } - - // For before/after notifications - return `${amount} ${unit} ${when === 'before' ? 'before' : 'after'} due` + return `${value} ${unit} ${type === 'before' ? 'before' : 'after'} due` } -const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { +const NotificationTemplate = ({ + maxNotifications = 5, + onChange, + value, + showTimeline = true, +}) => { const [templateName, setTemplateName] = useState( value?.name || 'New Notification Template', ) - const [notifications, setNotifications] = useState([ - value?.notifications || { - amount: 0, - when: 'ondue', - }, - ]) - const [error, setError] = useState(null) + const [notifications, setNotifications] = useState( + value?.templates || + JSON.parse(localStorage.getItem('defaultNotificationTemplate')) || + [], + ) + const [error, setError] = useState(null) + const [showSaveDefault, setShowSaveDefault] = useState(false) // Create a map of notification indices for timeline display const [notificationIndexMap, setNotificationIndexMap] = useState({}) - // Use useCallback to memoize the function const updateNotificationIndices = useCallback(() => { // Sort notifications for consistent ordering const sorted = [...notifications].sort((a, b) => { // Always ensure correct ordering: Before Due -> On Due -> After Due - if (a.when !== b.when) { - // Before Due always comes first - if (a.when === 'before') return -1 - if (b.when === 'before') return 1 + if (a.type !== b.type) { + // Before Due first + if (a.type === 'before') return -1 + if (b.type === 'before') return 1 // On Due comes before After Due - if (a.when === 'ondue') return -1 - if (b.when === 'ondue') return 1 - - // Default case (should not be reached with our options) + if (a.type === 'ondue') return -1 + if (b.type === 'ondue') return 1 + // DEFAULT CASE ( NOT SURE FOR FUTURE )? return 0 } - // Convert everything to minutes for consistent comparison const getMinutes = notif => { - const { amount, unit } = notif - let minutes = amount + const { value, unit } = notif + let minutes = value if (unit === 'hours') minutes *= 60 if (unit === 'days') minutes *= 24 * 60 return minutes @@ -83,17 +82,16 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { // For After Due: sort in ascending order (closest to due first) const aMinutes = getMinutes(a) const bMinutes = getMinutes(b) - return a.when === 'before' ? bMinutes - aMinutes : aMinutes - bMinutes + return a.type === 'before' ? bMinutes - aMinutes : aMinutes - bMinutes }) - // Create index mapping const indexMap = {} sorted.forEach((item, index) => { const originalIdx = notifications.findIndex( n => - n.amount === item.amount && + n.value === item.value && n.unit === item.unit && - n.when === item.when, + n.type === item.type, ) indexMap[originalIdx] = index + 1 }) @@ -104,27 +102,25 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { // Sort notifications and update the index mapping useEffect(() => { updateNotificationIndices() - // Clear any errors when notifications change setError(null) }, [updateNotificationIndices]) // Notify parent component of changes including the template name useEffect(() => { if (onChange) { - onChange({ name: templateName, notifications }) + onChange({ notifications }) } - }, [templateName, notifications, onChange]) + }, [notifications, onChange]) // Validates if a notification configuration already exists const isDuplicate = (notification, currentIdx = -1) => { return notifications.some((n, idx) => { - // Skip comparing with itself when editing if (idx === currentIdx) return false return ( - n.amount === notification.amount && + n.value === notification.value && n.unit === notification.unit && - n.when === notification.when + n.type === notification.type ) }) } @@ -136,17 +132,17 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { } // Special handling for "On Due" option - if (field === 'when' && value === 'ondue') { + if (field === 'type' && value === 'ondue') { // Set default values for On Due (not applicable) updatedNotification = { ...updatedNotification, - amount: 0, - unit: 'minutes', // default unit, not displayed to user + value: 1, + unit: 'minutes', } // Check if another notification is already "On Due" const existingOnDue = notifications.findIndex( - (n, i) => i !== idx && n.when === 'ondue', + (n, i) => i !== idx && n.type === 'ondue', ) if (existingOnDue !== -1) { @@ -157,7 +153,6 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { } } - // Check for duplicates when changing a notification if (isDuplicate(updatedNotification, idx)) { setError( 'This notification setting already exists. Please use a different timing.', @@ -172,12 +167,9 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { setError(null) } - const handleNameChange = e => { - setTemplateName(e.target.value) - } const addSmartNotification = type => { if (notifications.length >= maxNotifications) return - + setShowSaveDefault(true) let newNotification let suggestions = [] @@ -185,28 +177,28 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { case 'reminder': // Suggest common reminder times that don't exist suggestions = [ - { amount: 1, unit: 'hours', when: 'before' }, - { amount: 1, unit: 'days', when: 'before' }, - { amount: 30, unit: 'minutes', when: 'before' }, - { amount: 2, unit: 'hours', when: 'before' }, - { amount: 3, unit: 'days', when: 'before' }, + { value: 1, unit: 'hours', type: 'before' }, + { value: 1, unit: 'days', type: 'before' }, + { value: 30, unit: 'minutes', type: 'before' }, + { value: 2, unit: 'hours', type: 'before' }, + { value: 3, unit: 'days', type: 'before' }, ] break case 'due': - if (notifications.some(n => n.when === 'ondue')) { + if (notifications.some(n => n.type === 'ondue')) { setError('Only one "Due Alert" notification is allowed.') return } - newNotification = { amount: 0, unit: 'minutes', when: 'ondue' } + newNotification = { value: 0, unit: 'minutes', type: 'ondue' } break case 'followup': suggestions = [ - { amount: 1, unit: 'hours', when: 'after' }, - { amount: 1, unit: 'days', when: 'after' }, - { amount: 3, unit: 'days', when: 'after' }, - { amount: 1, unit: 'weeks', when: 'after' }, + { value: 1, unit: 'hours', type: 'after' }, + { value: 1, unit: 'days', type: 'after' }, + { value: 3, unit: 'days', type: 'after' }, + { value: 1, unit: 'weeks', type: 'after' }, ] break } @@ -226,14 +218,14 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { (a, b) => { // Convert everything to minutes for consistent comparison const getMinutes = notif => { - const { amount, unit, when } = notif + const { value, unit, type } = notif // On Due is exactly at due date (0 minutes) - if (when === 'ondue') return 0 + if (type === 'ondue') return 0 - let minutes = amount + let minutes = value if (unit === 'hours') minutes *= 60 if (unit === 'days') minutes *= 24 * 60 - return when === 'before' ? -minutes : minutes + return type === 'before' ? -minutes : minutes } return getMinutes(a) - getMinutes(b) @@ -249,20 +241,19 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { setNotifications(updated) onChange && onChange(updated) } - // Visualization: improved timeline with better scaling const renderTimeline = () => { // Sort notifications chronologically const sorted = [...notifications].sort((a, b) => { // Convert everything to minutes for consistent comparison const getMinutes = notif => { - const { amount, unit, when } = notif + const { value, unit, type } = notif // On Due is exactly at due date (0 minutes) - if (when === 'ondue') return 0 + if (type === 'ondue') return 0 - let minutes = amount + let minutes = value if (unit === 'hours') minutes *= 60 if (unit === 'days') minutes *= 24 * 60 - return when === 'before' ? -minutes : minutes + return type === 'before' ? -minutes : minutes } return getMinutes(a) - getMinutes(b) @@ -274,27 +265,24 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { // Find the original index of this item in notifications array const originalIdx = notifications.findIndex( n => - n.amount === item.amount && + n.value === item.value && n.unit === item.unit && - n.when === item.when, + n.type === item.type, ) notificationIndexMap[originalIdx] = index + 1 }) // Get min and max notification times for dynamic scaling const minutesValues = sorted.map(n => { - // On Due is exactly at due date (0 minutes) - if (n.when === 'ondue') return 0 + if (n.type === 'ondue') return 0 - let minutes = n.amount + let minutes = n.value if (n.unit === 'hours') minutes *= 60 if (n.unit === 'days') minutes *= 24 * 60 - return n.when === 'before' ? -minutes : minutes + return n.type === 'before' ? -minutes : minutes }) - // Find min (before) and max (after) notification times const minBefore = Math.min(0, ...minutesValues) // Default to 0 if no "before" notifications const maxAfter = Math.max(0, ...minutesValues) // Default to 0 if no "after" notifications - // Dynamic scaling based on notification range const getPositionPercent = minutes => { // Due date is always at center (50%) if (minutes === 0) return 50 @@ -314,7 +302,7 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { return ( - + Notification Timeline { display: 'flex', flexDirection: 'column', position: 'relative', - height: 100, + height: 90, bgcolor: 'background.level1', borderRadius: 'md', p: 2, @@ -375,11 +363,11 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { {sorted.map((n, i) => { // Convert to minutes for consistent scale let minutes = 0 - if (n.when !== 'ondue') { - minutes = n.amount + if (n.type !== 'ondue') { + minutes = n.value if (n.unit === 'hours') minutes *= 60 if (n.unit === 'days') minutes *= 24 * 60 - if (n.when === 'before') minutes = -minutes + if (n.type === 'before') minutes = -minutes } // On Due notifications are always at the due date (0 minutes) @@ -394,9 +382,9 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { left: `${percent}%`, transform: 'translateX(-50%)', color: - n.when === 'before' + n.type === 'before' ? 'primary.600' - : n.when === 'ondue' + : n.type === 'ondue' ? 'warning.600' : 'success.600', display: 'flex', @@ -418,11 +406,11 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { size={'sm'} variant={'solid'} color={ - n.when === 'before' - ? 'primary' - : n.when === 'ondue' + n.type === 'before' + ? 'success' + : n.type === 'ondue' ? 'warning' - : 'success' + : 'danger' } sx={{ '--Badge-paddingX': '4px', @@ -452,22 +440,24 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { return ( - + {/* Schedule Name - + */} {/* Template Name Field */} - + {/* Template Name @@ -477,7 +467,7 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { placeholder='Enter template name' sx={{ width: '100%' }} /> - + */} {error && ( { sx={{ display: 'flex', alignItems: 'center', - justifyContent: 'center', - width: 30, - mr: 1, + justifyContent: 'start', + width: 18, + flexShrink: 0, }} > @@ -512,23 +502,23 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => { sx={{ '--Badge-minHeight': '20px', '--Badge-fontSize': '0.75rem', + // centering the badge: }} color={ - n.when === 'before' - ? 'primary' - : n.when === 'ondue' + n.type === 'before' + ? 'success' + : n.type === 'ondue' ? 'warning' - : 'success' + : 'danger' } > - {/* Empty box to attach badge to */} - handleChange(idx, 'amount', Math.max(1, Number(e.target.value))) + handleChange(idx, 'value', Math.max(1, Number(e.target.value))) } sx={{ width: 70, mr: 1, - opacity: n.when === 'ondue' ? 0.6 : 1, - ...(n.when === 'ondue' && { + opacity: n.type === 'ondue' ? 0.6 : 1, + ...(n.type === 'ondue' && { '& input': { textAlign: 'center', }, }), }} size={'sm'} - placeholder={n.when === 'ondue' ? '—' : ''} + placeholder={n.type === 'ondue' ? '—' : ''} /> +