feat: Enhance UserPoints component with improved filtering and layout
- Updated UserPoints component to include a more user-friendly filter bar with enhanced styling. - Added a summary section to display the current filter context. - Refactored user selection and time period filtering logic for better clarity and performance. - Improved the layout of points cards and history sections for better visual hierarchy. - Integrated a bar chart for visual representation of points over time. - Updated redeem points functionality with better user feedback. feat: Add keyboard shortcuts in AddTaskModal for improved usability - Implemented keyboard shortcuts for adding descriptions, subtasks, and due dates. - Enhanced user experience by providing visual hints for keyboard shortcuts. - Refactored task creation logic to streamline the process. fix: Refactor ChoreActionMenu to handle mouse events and improve accessibility - Added mouse enter and leave event handlers for better interaction feedback. - Adjusted menu positioning for improved usability. refactor: Update RichTextEditor to support focus handling from parent components - Converted RichTextEditor to use forwardRef for better integration with parent components. - Exposed focus and blur methods for external control. - Improved image upload handling with better error management. fix: Adjust SubTask component to handle Enter key behavior correctly - Modified key event handling to prevent unintended task creation when holding meta or ctrl keys. - Added autoFocus prop to new task input for better user experience.
This commit is contained in:
@@ -2,6 +2,13 @@ import moment from 'moment'
|
||||
import { TASK_COLOR } from './Colors.jsx'
|
||||
|
||||
const priorityOrder = [1, 2, 3, 4, 0]
|
||||
// ChoreGrouperOptions enum:
|
||||
export const GROUPING_OPTIONS = {
|
||||
SMART: 'default',
|
||||
DUE_DATE: 'due_date',
|
||||
PRIORITY: 'priority',
|
||||
LABELS: 'labels',
|
||||
}
|
||||
|
||||
export const ChoresGrouper = (groupBy, chores, filter) => {
|
||||
if (filter) {
|
||||
@@ -12,6 +19,110 @@ export const ChoresGrouper = (groupBy, chores, filter) => {
|
||||
chores.sort(ChoreSorter)
|
||||
var groups = []
|
||||
switch (groupBy) {
|
||||
case 'default':
|
||||
// same as due_date but hide empty groups: and if status is 1 or 2 have seperated catigory as Started:
|
||||
var groupRaw = {
|
||||
Started: [],
|
||||
Today: [],
|
||||
Tomorrow: [],
|
||||
'Next 7 Days': [],
|
||||
'Later This Month': [],
|
||||
Future: [],
|
||||
Overdue: [],
|
||||
Anytime: [],
|
||||
}
|
||||
chores.forEach(chore => {
|
||||
if (chore.status === 1 || chore.status === 2) {
|
||||
groupRaw['Started'].push(chore)
|
||||
} else if (chore.nextDueDate === null) {
|
||||
groupRaw['Anytime'].push(chore)
|
||||
} else if (new Date(chore.nextDueDate) < new Date()) {
|
||||
groupRaw['Overdue'].push(chore)
|
||||
} else if (
|
||||
new Date(chore.nextDueDate).toDateString() ===
|
||||
new Date().toDateString()
|
||||
) {
|
||||
groupRaw['Today'].push(chore)
|
||||
} else if (
|
||||
new Date(chore.nextDueDate).toDateString() ===
|
||||
new Date(Date.now() + 24 * 60 * 60 * 1000).toDateString()
|
||||
) {
|
||||
groupRaw['Tomorrow'].push(chore)
|
||||
} else if (
|
||||
new Date(chore.nextDueDate) <
|
||||
new Date(Date.now() + 8 * 24 * 60 * 60 * 1000) &&
|
||||
new Date(chore.nextDueDate) >
|
||||
new Date(Date.now() + 24 * 60 * 60 * 1000)
|
||||
) {
|
||||
groupRaw['Next 7 Days'].push(chore)
|
||||
} else if (
|
||||
new Date(chore.nextDueDate).getMonth() === new Date().getMonth() &&
|
||||
new Date(chore.nextDueDate).getFullYear() === new Date().getFullYear()
|
||||
) {
|
||||
groupRaw['Later This Month'].push(chore)
|
||||
} else {
|
||||
groupRaw['Future'].push(chore)
|
||||
}
|
||||
})
|
||||
groups = []
|
||||
if (groupRaw['Started'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Started',
|
||||
content: groupRaw['Started'],
|
||||
color: TASK_COLOR.STARTED,
|
||||
})
|
||||
}
|
||||
if (groupRaw['Overdue'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Overdue',
|
||||
content: groupRaw['Overdue'],
|
||||
color: TASK_COLOR.OVERDUE,
|
||||
})
|
||||
}
|
||||
if (groupRaw['Today'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Today',
|
||||
content: groupRaw['Today'],
|
||||
color: TASK_COLOR.TODAY,
|
||||
})
|
||||
}
|
||||
if (groupRaw['Tomorrow'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Tomorrow',
|
||||
content: groupRaw['Tomorrow'],
|
||||
color: TASK_COLOR.TOMORROW,
|
||||
})
|
||||
}
|
||||
if (groupRaw['Next 7 Days'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Next 7 Days',
|
||||
content: groupRaw['Next 7 Days'],
|
||||
color: TASK_COLOR.NEXT_7_DAYS,
|
||||
})
|
||||
}
|
||||
if (groupRaw['Later This Month'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Later This Month',
|
||||
content: groupRaw['Later This Month'],
|
||||
color: TASK_COLOR.LATER_THIS_MONTH,
|
||||
})
|
||||
}
|
||||
if (groupRaw['Future'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Future',
|
||||
content: groupRaw['Future'],
|
||||
color: TASK_COLOR.FUTURE,
|
||||
})
|
||||
}
|
||||
if (groupRaw['Anytime'].length > 0) {
|
||||
groups.push({
|
||||
name: 'Anytime',
|
||||
content: groupRaw['Anytime'],
|
||||
color: TASK_COLOR.ANYTIME,
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case 'due_date':
|
||||
var groupRaw = {
|
||||
Today: [],
|
||||
|
||||
@@ -123,6 +123,20 @@ const MarkChoreComplete = (id, body, completedDate, performer) => {
|
||||
})
|
||||
}
|
||||
|
||||
const StartChore = id => {
|
||||
return Fetch(`/chores/${id}/start`, {
|
||||
method: 'PUT',
|
||||
headers: HEADERS(),
|
||||
})
|
||||
}
|
||||
|
||||
const PauseChore = id => {
|
||||
return Fetch(`/chores/${id}/pause`, {
|
||||
method: 'PUT',
|
||||
headers: HEADERS(),
|
||||
})
|
||||
}
|
||||
|
||||
const CompleteSubTask = (id, choreId, completedAt) => {
|
||||
var markChoreURL = `/chores/${choreId}/subtask`
|
||||
return Fetch(markChoreURL, {
|
||||
@@ -204,14 +218,6 @@ const UpdateChoreHistory = (choreId, id, choreHistory) => {
|
||||
})
|
||||
}
|
||||
|
||||
const UpdateChoreStatus = (choreId, status) => {
|
||||
return Fetch(`/chores/${choreId}/status`, {
|
||||
method: 'PUT',
|
||||
headers: HEADERS(),
|
||||
body: JSON.stringify({ status }),
|
||||
})
|
||||
}
|
||||
|
||||
const GetAllCircleMembers = async () => {
|
||||
const resp = await Fetch(`/circles/members`, {
|
||||
method: 'GET',
|
||||
@@ -553,11 +559,49 @@ const GetStorageUsage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// Timer/TimeSession API functions
|
||||
const GetChoreTimer = choreId => {
|
||||
return Fetch(`/chores/${choreId}/timer`, {
|
||||
method: 'GET',
|
||||
headers: HEADERS(),
|
||||
})
|
||||
}
|
||||
|
||||
const UpdateTimeSession = (choreId, sessionId, sessionData) => {
|
||||
return Fetch(`/chores/${choreId}/timer/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: HEADERS(),
|
||||
body: JSON.stringify(sessionData),
|
||||
})
|
||||
}
|
||||
|
||||
const DeleteTimeSession = (choreId, sessionId) => {
|
||||
return Fetch(`/chores/${choreId}/timer/${sessionId}`, {
|
||||
method: 'DELETE',
|
||||
headers: HEADERS(),
|
||||
})
|
||||
}
|
||||
|
||||
const ResetChoreTimer = choreId => {
|
||||
return Fetch(`/chores/${choreId}/timer/reset`, {
|
||||
method: 'PUT',
|
||||
headers: HEADERS(),
|
||||
})
|
||||
}
|
||||
|
||||
const ClearChoreTimer = choreId => {
|
||||
return Fetch(`/chores/${choreId}/timer`, {
|
||||
method: 'DELETE',
|
||||
headers: HEADERS(),
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
AcceptCircleMemberRequest,
|
||||
ArchiveChore,
|
||||
CancelSubscription,
|
||||
ChangePassword,
|
||||
ClearChoreTimer,
|
||||
CompleteSubTask,
|
||||
ConfirmMFA,
|
||||
CreateChore,
|
||||
@@ -570,6 +614,7 @@ export {
|
||||
DeleteLabel,
|
||||
DeleteLongLiveToken,
|
||||
DeleteThing,
|
||||
DeleteTimeSession,
|
||||
DisableMFA,
|
||||
GetAllCircleMembers,
|
||||
GetAllUsers,
|
||||
@@ -577,6 +622,7 @@ export {
|
||||
GetChoreByID,
|
||||
GetChoreDetailById,
|
||||
GetChoreHistory,
|
||||
GetChoreTimer,
|
||||
GetChores,
|
||||
GetChoresHistory,
|
||||
GetChoresNew,
|
||||
@@ -594,27 +640,30 @@ export {
|
||||
JoinCircle,
|
||||
LeaveCircle,
|
||||
MarkChoreComplete,
|
||||
PauseChore,
|
||||
PutNotificationTarget,
|
||||
PutWebhookURL,
|
||||
RedeemPoints,
|
||||
RefreshToken,
|
||||
RegenerateBackupCodes,
|
||||
ResetChoreTimer,
|
||||
ResetPassword,
|
||||
SaveChore,
|
||||
SaveThing,
|
||||
SetupMFA,
|
||||
SkipChore,
|
||||
StartChore,
|
||||
UnArchiveChore,
|
||||
UpdateChoreAssignee,
|
||||
UpdateChoreHistory,
|
||||
UpdateChorePriority,
|
||||
UpdateChoreStatus,
|
||||
UpdateDueDate,
|
||||
UpdateLabel,
|
||||
UpdateMemberRole,
|
||||
UpdateNotificationTarget,
|
||||
UpdatePassword,
|
||||
UpdateThingState,
|
||||
UpdateTimeSession,
|
||||
UpdateUserDetails,
|
||||
VerifyMFA,
|
||||
createChore,
|
||||
|
||||
63
src/utils/PlatformUtils.js
Normal file
63
src/utils/PlatformUtils.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Utility functions for platform detection
|
||||
*/
|
||||
|
||||
/**
|
||||
* Detects if the current platform is macOS using modern APIs with fallback
|
||||
* @returns {boolean} True if running on macOS, false otherwise
|
||||
*/
|
||||
export const isMacOS = () => {
|
||||
// Modern approach using User-Agent Client Hints API
|
||||
if (navigator.userAgentData) {
|
||||
return navigator.userAgentData.platform === 'macOS'
|
||||
}
|
||||
|
||||
// Fallback for older browsers
|
||||
return /Mac|iPhone|iPad|iPod/.test(navigator.userAgent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate keyboard shortcut text for the current platform
|
||||
* @param {string} key - The key combination (e.g., 'F', 'K', 'S')
|
||||
* @param {boolean} withCtrl - Whether to include Ctrl/Cmd modifier
|
||||
* @param {boolean} withShift - Whether to include Shift modifier
|
||||
* @returns {string} Platform-appropriate keyboard shortcut text
|
||||
*/
|
||||
export const getKeyboardShortcut = (
|
||||
key,
|
||||
withCtrl = true,
|
||||
withShift = false,
|
||||
) => {
|
||||
let shortcut = ''
|
||||
|
||||
if (withCtrl) {
|
||||
const modifier = isMacOS() ? '⌘' : 'Ctrl+'
|
||||
shortcut += modifier
|
||||
}
|
||||
|
||||
if (withShift) {
|
||||
if (isMacOS()) {
|
||||
shortcut += '⇧'
|
||||
} else {
|
||||
shortcut += 'Shift+'
|
||||
}
|
||||
}
|
||||
|
||||
shortcut += key
|
||||
return shortcut
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets common keyboard shortcuts for the current platform
|
||||
*/
|
||||
export const getCommonShortcuts = () => ({
|
||||
search: getKeyboardShortcut('F'),
|
||||
newTask: getKeyboardShortcut('K'),
|
||||
selectAll: getKeyboardShortcut('A'),
|
||||
multiSelect: getKeyboardShortcut('S'),
|
||||
save: getKeyboardShortcut('S'),
|
||||
copy: getKeyboardShortcut('C'),
|
||||
paste: getKeyboardShortcut('V'),
|
||||
undo: getKeyboardShortcut('Z'),
|
||||
redo: getKeyboardShortcut('Z', true, true), // Ctrl/Cmd + Shift + Z
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Network } from '@capacitor/network'
|
||||
import { Preferences } from '@capacitor/preferences'
|
||||
import Cookies from 'js-cookie'
|
||||
import murmurhash from 'murmurhash'
|
||||
@@ -82,11 +81,11 @@ export async function Fetch(url, options) {
|
||||
const baseURL = apiManager.getApiURL()
|
||||
const fullURL = `${baseURL}${url}`
|
||||
|
||||
const networkStatus = await Network.getStatus()
|
||||
// const networkStatus = await Network.getStatus()
|
||||
|
||||
if (!networkStatus.connected) {
|
||||
return handleOfflineRequest(fullURL, options)
|
||||
}
|
||||
// if (!networkStatus.connected) {
|
||||
// return handleOfflineRequest(fullURL, options)
|
||||
// }
|
||||
|
||||
// Online: Perform the fetch
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user