Improve task page

This commit is contained in:
Chris Veleris 2025-07-31 17:23:37 +03:00 committed by Chris
parent a9fa67d616
commit 62b2143689
6 changed files with 50 additions and 105 deletions

View file

@ -363,22 +363,25 @@ const TaskDetails: React.FC = () => {
<div className="lg:col-span-2 space-y-8">
{/* Notes Section - Always Visible */}
<div>
<h4 className="text-base font-light text-gray-900 dark:text-gray-100 mb-4">
<h4 className="text-base font-semibold text-gray-900 dark:text-gray-100 mb-4">
{t('task.content', 'Content')}
</h4>
{task.note ? (
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6">
<div className="rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 border-gray-50 dark:border-gray-800 p-6">
<MarkdownRenderer
content={task.note}
className="prose dark:prose-invert max-w-none"
/>
</div>
) : (
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6">
<div className="rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 border-gray-50 dark:border-gray-800 p-6">
<div className="flex flex-col items-center justify-center py-8 text-gray-500 dark:text-gray-400">
<PencilSquareIcon className="h-12 w-12 mb-3 opacity-50" />
<span className="text-sm text-center">
{t('task.noNotes', 'No content added yet')}
{t(
'task.noNotes',
'No content added yet'
)}
</span>
</div>
</div>
@ -387,7 +390,7 @@ const TaskDetails: React.FC = () => {
{/* Subtasks Section - Always Visible */}
<div>
<h4 className="text-base font-light text-gray-900 dark:text-gray-100 mb-4">
<h4 className="text-base font-semibold text-gray-900 dark:text-gray-100 mb-4">
{t('task.subtasks', 'Subtasks')}
</h4>
{subtasks.length > 0 ? (
@ -472,11 +475,14 @@ const TaskDetails: React.FC = () => {
))}
</div>
) : (
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6">
<div className="rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 border-gray-50 dark:border-gray-800 p-6">
<div className="flex flex-col items-center justify-center py-8 text-gray-500 dark:text-gray-400">
<ListBulletIcon className="h-12 w-12 mb-3 opacity-50" />
<span className="text-sm text-center">
{t('task.noSubtasks', 'No subtasks yet')}
{t(
'task.noSubtasks',
'No subtasks yet'
)}
</span>
</div>
</div>
@ -486,10 +492,10 @@ const TaskDetails: React.FC = () => {
{/* Right Column - Recent Activity */}
<div>
<h4 className="text-base font-light text-gray-900 dark:text-gray-100 mb-4">
<h4 className="text-base font-semibold text-gray-900 dark:text-gray-100 mb-4">
{t('task.recentActivity', 'Recent Activity')}
</h4>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6">
<div className="rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 border-gray-50 dark:border-gray-800 p-6">
<TaskTimeline taskId={task.id} />
</div>
</div>
@ -497,8 +503,6 @@ const TaskDetails: React.FC = () => {
</div>
{/* End of main content sections */}
{/* Task Modal for Editing */}
<TaskModal
isOpen={isTaskModalOpen}

View file

@ -474,7 +474,7 @@ const TaskModal: React.FC<TaskModalProps> = ({
WebkitOverflowScrolling: 'touch',
}}
>
<form
<form
className="h-full"
onSubmit={(e) => {
e.preventDefault();

View file

@ -8,16 +8,8 @@ import {
} from '../../utils/taskEventService';
import {
ClockIcon,
CheckCircleIcon,
ExclamationTriangleIcon,
PencilIcon,
TagIcon,
CalendarIcon,
FolderIcon,
PlayIcon,
ArchiveBoxIcon,
SparklesIcon,
AdjustmentsHorizontalIcon,
} from '@heroicons/react/24/outline';
interface TaskTimelineProps {
@ -44,9 +36,12 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ taskId }) => {
try {
const timeline = await getTaskTimeline(taskId);
// Sort events by created_at in descending order (most recent first)
const sortedTimeline = timeline.sort((a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
const sortedTimeline = timeline.sort(
(a, b) =>
new Date(b.created_at).getTime() -
new Date(a.created_at).getTime()
);
// Show all events, scrolling will handle display
setEvents(sortedTimeline);
} catch (err) {
console.error('Error fetching task timeline:', err);
@ -59,75 +54,6 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ taskId }) => {
fetchTimeline();
}, [taskId]);
const getEventIcon = (eventType: string, newValue?: any) => {
const iconClass = 'h-3.5 w-3.5';
switch (eventType) {
case 'created':
return (
<SparklesIcon className={`${iconClass} text-blue-500`} />
);
case 'status_changed':
if (newValue?.status === 1)
return (
<PlayIcon className={`${iconClass} text-yellow-500`} />
);
if (newValue?.status === 2)
return (
<CheckCircleIcon
className={`${iconClass} text-green-500`}
/>
);
if (newValue?.status === 3)
return (
<ArchiveBoxIcon
className={`${iconClass} text-gray-500`}
/>
);
return (
<AdjustmentsHorizontalIcon
className={`${iconClass} text-blue-500`}
/>
);
case 'completed':
return (
<CheckCircleIcon
className={`${iconClass} text-green-500`}
/>
);
case 'priority_changed':
return (
<ExclamationTriangleIcon
className={`${iconClass} text-orange-500`}
/>
);
case 'due_date_changed':
return (
<CalendarIcon className={`${iconClass} text-purple-500`} />
);
case 'project_changed':
return (
<FolderIcon className={`${iconClass} text-indigo-500`} />
);
case 'name_changed':
case 'description_changed':
case 'note_changed':
return <PencilIcon className={`${iconClass} text-gray-500`} />;
case 'tags_changed':
return <TagIcon className={`${iconClass} text-pink-500`} />;
case 'archived':
return (
<ArchiveBoxIcon className={`${iconClass} text-gray-500`} />
);
case 'today_changed':
return (
<CalendarIcon className={`${iconClass} text-blue-600`} />
);
default:
return <ClockIcon className={`${iconClass} text-gray-400`} />;
}
};
const getTranslatedStatusLabel = (status: number | string): string => {
// Handle both numeric and string status values
const statusMap: Record<string | number, string> = {
@ -138,12 +64,12 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ taskId }) => {
3: t('status.archived'),
4: t('status.waiting'),
// String values
'not_started': t('status.notStarted'),
'in_progress': t('status.inProgress'),
'done': t('status.completed'),
'completed': t('status.completed'),
'archived': t('status.archived'),
'waiting': t('status.waiting'),
not_started: t('status.notStarted'),
in_progress: t('status.inProgress'),
done: t('status.completed'),
completed: t('status.completed'),
archived: t('status.archived'),
waiting: t('status.waiting'),
};
return statusMap[status] || t('status.unknown', { status });
@ -180,6 +106,14 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ taskId }) => {
}
return t('timeline.events.dueDateChanged');
}
case 'recurrence_end_date_changed': {
const oldDate = old_value?.recurrence_end_date;
const newDate = new_value?.recurrence_end_date;
if (oldDate || newDate) {
return `${t('timeline.events.recurrenceEndDate')}: ${formatDate(oldDate)}${formatDate(newDate)}`;
}
return t('timeline.events.recurrenceEndDateChanged');
}
case 'name_changed':
return t('timeline.events.nameUpdated');
case 'description_changed':
@ -201,20 +135,24 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ taskId }) => {
const formatDate = (dateString: string | null) => {
if (!dateString) return t('timeline.events.none');
// Handle ISO date strings (e.g., "2025-07-15T00:00:00.000Z")
const date = new Date(dateString);
// Check if it's today, tomorrow, or yesterday
const today = new Date().toISOString().split('T')[0];
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0];
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0];
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000)
.toISOString()
.split('T')[0];
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000)
.toISOString()
.split('T')[0];
const dateOnly = date.toISOString().split('T')[0];
if (dateOnly === today) return t('dateIndicators.today');
if (dateOnly === tomorrow) return t('dateIndicators.tomorrow');
if (dateOnly === yesterday) return t('dateIndicators.yesterday');
// Return formatted date (e.g., "Jul 15, 2025")
return date.toLocaleDateString(undefined, {
year: 'numeric',
@ -278,7 +216,7 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ taskId }) => {
}
return (
<div className="h-full overflow-y-auto">
<div className="max-h-[36rem] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 dark:scrollbar-thumb-gray-600 scrollbar-track-transparent">
<div className="space-y-2">
{events.map((event) => (
<div key={event.id} className="relative">

View file

@ -7,6 +7,7 @@ export interface TaskEvent {
| 'status_changed'
| 'priority_changed'
| 'due_date_changed'
| 'recurrence_end_date_changed'
| 'project_changed'
| 'name_changed'
| 'description_changed'

View file

@ -186,7 +186,7 @@ export const getEventTypeLabel = (eventType: string): string => {
export const getStatusLabel = (status: number): string => {
const statusLabels: Record<number, string> = {
0: 'Not Started',
1: 'In Progress',
1: 'In Progress',
2: 'Completed',
3: 'Archived',
4: 'Waiting',

View file

@ -131,6 +131,7 @@
"statusChanged": "Status changed",
"priorityChanged": "Priority changed",
"dueDateChanged": "Due date changed",
"recurrenceEndDateChanged": "Recurrence end date changed",
"nameUpdated": "Name updated",
"descriptionUpdated": "Description updated",
"noteUpdated": "Note updated",
@ -141,6 +142,7 @@
"status": "Status",
"priority": "Priority",
"dueDate": "Due date",
"recurrenceEndDate": "Recurrence end date",
"none": "None"
}
},