Improve task page
This commit is contained in:
parent
a9fa67d616
commit
62b2143689
6 changed files with 50 additions and 105 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ const TaskModal: React.FC<TaskModalProps> = ({
|
|||
WebkitOverflowScrolling: 'touch',
|
||||
}}
|
||||
>
|
||||
<form
|
||||
<form
|
||||
className="h-full"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export interface TaskEvent {
|
|||
| 'status_changed'
|
||||
| 'priority_changed'
|
||||
| 'due_date_changed'
|
||||
| 'recurrence_end_date_changed'
|
||||
| 'project_changed'
|
||||
| 'name_changed'
|
||||
| 'description_changed'
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue