import '@meauxt/react-swipeable-list/dist/styles.css' import { SwipeableList, SwipeableListItem, SwipeAction, TrailingActions, Type as ListType, } from '@meauxt/react-swipeable-list' import { Add, MoreVert, Task } from '@mui/icons-material' import DeleteIcon from '@mui/icons-material/Delete' import EditIcon from '@mui/icons-material/Edit' import { Avatar, Box, Chip, CircularProgress, Container, IconButton, Stack, Typography, } from '@mui/joy' import { useQueryClient } from '@tanstack/react-query' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate, useSearchParams } from 'react-router-dom' import { useChores } from '../../queries/ChoreQueries' import { useUserProfile } from '../../queries/UserQueries' import { getTextColorFromBackgroundColor } from '../../utils/Colors' import { DeleteProject } from '../../utils/Fetcher' import { getIconComponent } from '../../utils/ProjectIcons' import { getSafeBottomStyles } from '../../utils/SafeAreaUtils' import { useProjectFilter } from '../Chores/hooks/useProjectFilter' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import ProjectModal from '../Modals/Inputs/ProjectModal' import { useProjects } from './ProjectQueries' const ProjectCardContent = ({ currentUserId, onCardClick, onToggleActions, project, taskCounts = {}, }) => { const { t } = useTranslation('projects') // Check if current user owns this project const isOwnedByCurrentUser = project.created_by === currentUserId const isDefaultProject = project.id === 'default' const taskCount = taskCounts[project.id] || 0 return ( {/* Project Avatar */} {project.icon ? ( (() => { const IconComponent = getIconComponent(project.icon) return ( ) })() ) : ( <> )} {/* Content - Center */} {/* Project Name */} {project.name} {isDefaultProject && ( {t('defaultChip')} )} {/* Project Info */} {project.description && ( {project.description} )} } sx={{ fontSize: 10, height: 18, px: 0.75, bgcolor: 'primary.softBg', color: 'primary.500', }} > {t('tasks', { count: taskCount })} {!isOwnedByCurrentUser && !isDefaultProject && ( {t('shared')} )} {onToggleActions && ( { e.stopPropagation() onToggleActions() }} > )} ) } const ProjectView = () => { const { t } = useTranslation('projects') const { data: projects, isError, isProjectsLoading } = useProjects() const { data: userProfile } = useUserProfile() const { data: chores = { res: [] } } = useChores(false) // false to exclude archived const { data: projectsData = [], isLoading: projectsLoading } = useProjects() const { setSelectedProjectWithCache } = useProjectFilter( projectsData, !projectsLoading, ) const navigate = useNavigate() const [searchParams, setSearchParams] = useSearchParams() const [userProjects, setUserProjects] = useState([]) const [modalOpen, setModalOpen] = useState(false) const [currentProject, setCurrentProject] = useState(null) const [taskCounts, setTaskCounts] = useState({}) const queryClient = useQueryClient() const [confirmationModel, setConfirmationModel] = useState({}) const [showMoreInfoId, setShowMoreInfoId] = useState(null) const handleAddProject = () => { setCurrentProject(null) setModalOpen(true) } const handleEditProject = project => { setCurrentProject(project) setModalOpen(true) } const handleDeleteClicked = id => { const project = userProjects.find(p => p.id === id) setConfirmationModel({ isOpen: true, title: t('delete.title'), message: t('delete.message', { name: project?.name }), confirmText: t('common:delete'), color: 'danger', cancelText: t('common:cancel'), onClose: confirmed => { if (confirmed === true) { handleDeleteProject(id) } setConfirmationModel({}) }, }) } const handleDeleteProject = id => { DeleteProject(id).then(() => { const updatedProjects = userProjects.filter(project => project.id !== id) setUserProjects(updatedProjects) queryClient.invalidateQueries('projects') // If the deleted project was the active project, clear it const saved = localStorage.getItem('selectedProject') if (saved) { const savedProject = JSON.parse(saved) if (savedProject && savedProject.id === id) { setSelectedProjectWithCache(null) } } }) } const handleSaveProject = () => { setModalOpen(false) } const handleCardClick = project => { // Always navigate to MyChores with project filter when clicking on the card // For default project, use 'default', for others use project ID const projectIdentifier = project.id === 'default' ? 'default' : project.id setSelectedProjectWithCache(project) navigate(`/chores?project=${encodeURIComponent(projectIdentifier)}`) } useEffect(() => { if (projects) { setUserProjects(projects) } }, [projects]) // ?create=1 lets other surfaces (global search quick actions) land here with // the create modal already open. useEffect(() => { if (searchParams.get('create') !== '1') return setCurrentProject(null) setModalOpen(true) setSearchParams( params => { params.delete('create') return params }, { replace: true }, ) }, [searchParams, setSearchParams]) // Calculate real task counts from chores data useEffect(() => { if (chores && chores.res) { const choresList = chores.res const realCounts = {} // First, count tasks for the default project (tasks without a projectId) const defaultProjectCount = choresList.filter(chore => { const choreProjectId = chore.projectId || chore.project_id return ( !choreProjectId || choreProjectId === '' || choreProjectId === 'default' || choreProjectId === null ) }).length realCounts['default'] = defaultProjectCount // Then count tasks for each user project userProjects.forEach(project => { const choreCount = choresList.filter(chore => { const choreProjectId = chore.projectId || chore.project_id return choreProjectId === project.id }).length realCounts[project.id] = choreCount }) setTaskCounts(realCounts) } }, [chores, userProjects]) if (isProjectsLoading) { return ( ) } if (isError) { return ( {t('loadError')} ) } return ( {t('common:navigation.projects')} {t('blurb')} {/* Default project - not swipeable */} handleCardClick({ id: 'default', name: t('chores:toolbar.defaultProject'), icon: 'FolderOpen', color: '#1976d2', }) } /> {/* User projects - swipeable */} {userProjects.map(project => ( handleCardClick(project)} key={project.id} swipeActionOpen={ showMoreInfoId === project.id ? 'trailing' : null } trailingActions={ handleEditProject(project)}> {t('common:edit')} handleDeleteClicked(project.id)} > {t('common:delete')} } > { if (showMoreInfoId === project.id) { setShowMoreInfoId(null) } else { setShowMoreInfoId(project.id) } }} /> ))} {modalOpen && ( setModalOpen(false)} onSave={handleSaveProject} project={currentProject} /> )} ) } export default ProjectView