* Add next suggestions and remove console logs * Add pomodoro timer * Add pomodoro switch in settings * Fix pomodoro setting * Add timezones to settings * Fix an issue with password reset * Cleanup * Sort tags alphabetically * Clean up today's view * Add an indicator for repeatedly added to today * Refactor tags * Add due date today item * Move recurrence to the subtitle area * Fix today layout * Add a badge to Inbox items * Move inbox badge to sidebar * Add quotes and progress bar * Add translations for quotes * Fix test issues * Add helper script for docker local * Set up overdue tasks * Add linux/arm/v7 build to deploy script * Add linux/arm/v7 build to deploy script pt2 * Fix an issue with helmet and SSL * Add volume db persistence * Fix cog icon issues
188 lines
No EOL
5.9 KiB
TypeScript
188 lines
No EOL
5.9 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
PencilSquareIcon,
|
|
TrashIcon,
|
|
Squares2X2Icon,
|
|
} from '@heroicons/react/24/solid';
|
|
import ConfirmDialog from './Shared/ConfirmDialog';
|
|
import AreaModal from './Area/AreaModal';
|
|
import { useStore } from '../store/useStore';
|
|
import { fetchAreas, createArea, updateArea, deleteArea } from '../utils/areasService';
|
|
import { Area } from '../entities/Area';
|
|
|
|
const Areas: React.FC = () => {
|
|
const { t } = useTranslation();
|
|
const { areas, setAreas, setLoading, setError } = useStore((state) => state.areasStore);
|
|
|
|
const [isAreaModalOpen, setIsAreaModalOpen] = useState<boolean>(false);
|
|
const [selectedArea, setSelectedArea] = useState<Area | null>(null);
|
|
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState<boolean>(false);
|
|
const [areaToDelete, setAreaToDelete] = useState<Area | null>(null);
|
|
|
|
useEffect(() => {
|
|
const loadAreas = async () => {
|
|
try {
|
|
const areasData = await fetchAreas();
|
|
setAreas(areasData);
|
|
} catch (error) {
|
|
console.error('Error fetching areas:', error);
|
|
setError(true);
|
|
}
|
|
};
|
|
|
|
loadAreas();
|
|
}, []);
|
|
|
|
const handleSaveArea = async (areaData: Partial<Area>) => {
|
|
setLoading(true);
|
|
try {
|
|
if (areaData.id) {
|
|
await updateArea(areaData.id, {
|
|
name: areaData.name,
|
|
description: areaData.description,
|
|
});
|
|
} else {
|
|
await createArea({
|
|
name: areaData.name,
|
|
description: areaData.description,
|
|
});
|
|
}
|
|
const updatedAreas = await fetchAreas();
|
|
setAreas(updatedAreas);
|
|
} catch (error) {
|
|
console.error('Error saving area:', error);
|
|
setError(true);
|
|
} finally {
|
|
setLoading(false);
|
|
setIsAreaModalOpen(false);
|
|
setSelectedArea(null);
|
|
}
|
|
};
|
|
|
|
const handleEditArea = (area: Area) => {
|
|
setSelectedArea(area);
|
|
setIsAreaModalOpen(true);
|
|
};
|
|
|
|
const handleCreateArea = () => {
|
|
setSelectedArea(null);
|
|
setIsAreaModalOpen(true);
|
|
};
|
|
|
|
const openConfirmDialog = (area: Area) => {
|
|
setAreaToDelete(area);
|
|
setIsConfirmDialogOpen(true);
|
|
};
|
|
|
|
const handleDeleteArea = async () => {
|
|
if (!areaToDelete) return;
|
|
|
|
setLoading(true);
|
|
try {
|
|
await deleteArea(areaToDelete.id!);
|
|
const updatedAreas = await fetchAreas();
|
|
setAreas(updatedAreas);
|
|
setIsConfirmDialogOpen(false);
|
|
setAreaToDelete(null);
|
|
} catch (error) {
|
|
console.error('Error deleting area:', error);
|
|
setError(true);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const closeConfirmDialog = () => {
|
|
setIsConfirmDialogOpen(false);
|
|
setAreaToDelete(null);
|
|
};
|
|
|
|
return (
|
|
<div className="flex justify-center px-4 lg:px-2">
|
|
<div className="w-full max-w-5xl">
|
|
{/* Areas Header */}
|
|
<div className="flex items-center justify-between mb-8">
|
|
<div className="flex items-center">
|
|
<Squares2X2Icon className="h-6 w-6 mr-2 text-gray-900 dark:text-white" />
|
|
<h2 className="text-2xl font-light text-gray-900 dark:text-white">
|
|
{t('areas.title')}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Areas List */}
|
|
{areas.length === 0 ? (
|
|
<p className="text-gray-700 dark:text-gray-300">{t('areas.noAreasFound')}</p>
|
|
) : (
|
|
<ul className="space-y-2">
|
|
{areas.map((area) => (
|
|
<li
|
|
key={area.id}
|
|
className="bg-white dark:bg-gray-900 shadow rounded-lg p-4 flex justify-between items-center"
|
|
>
|
|
{/* Area Content */}
|
|
<div className="flex-grow overflow-hidden pr-4">
|
|
<Link
|
|
to={`/projects?area_id=${area.id}`}
|
|
className="text-md font-semibold text-gray-900 dark:text-gray-100 hover:underline block"
|
|
>
|
|
{area.name}
|
|
</Link>
|
|
{area.description && (
|
|
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1 truncate">
|
|
{area.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex space-x-2">
|
|
<button
|
|
onClick={() => handleEditArea(area)}
|
|
className="text-gray-500 hover:text-blue-700 dark:hover:text-blue-300 focus:outline-none"
|
|
aria-label={t('areas.editAreaAriaLabel', { name: area.name })}
|
|
title={t('areas.editAreaTitle', { name: area.name })}
|
|
>
|
|
<PencilSquareIcon className="h-5 w-5" />
|
|
</button>
|
|
<button
|
|
onClick={() => openConfirmDialog(area)}
|
|
className="text-gray-500 hover:text-red-700 dark:hover:text-red-300 focus:outline-none"
|
|
aria-label={t('areas.deleteAreaAriaLabel', { name: area.name })}
|
|
title={t('areas.deleteAreaTitle', { name: area.name })}
|
|
>
|
|
<TrashIcon className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
|
|
{/* AreaModal */}
|
|
{isAreaModalOpen && (
|
|
<AreaModal
|
|
isOpen={isAreaModalOpen}
|
|
onClose={() => setIsAreaModalOpen(false)}
|
|
onSave={handleSaveArea}
|
|
area={selectedArea}
|
|
/>
|
|
)}
|
|
|
|
{/* ConfirmDialog */}
|
|
{isConfirmDialogOpen && areaToDelete && (
|
|
<ConfirmDialog
|
|
title={t('modals.deleteArea.title')}
|
|
message={t('modals.deleteArea.message', { name: areaToDelete.name })}
|
|
onConfirm={handleDeleteArea}
|
|
onCancel={closeConfirmDialog}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Areas; |