* Move elements to tabs * Add priority * Fix priority * Priority mobile fix * fixup! Priority mobile fix * fixup! fixup! Priority mobile fix * fixup! fixup! fixup! Priority mobile fix * fixup! fixup! fixup! fixup! Priority mobile fix
179 lines
8.1 KiB
TypeScript
179 lines
8.1 KiB
TypeScript
import React, { useRef, useState, useEffect } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
PencilSquareIcon,
|
|
EyeIcon,
|
|
PencilIcon,
|
|
} from '@heroicons/react/24/outline';
|
|
import MarkdownRenderer from '../../Shared/MarkdownRenderer';
|
|
|
|
interface TaskContentCardProps {
|
|
content: string;
|
|
onUpdate: (newContent: string) => Promise<void>;
|
|
}
|
|
|
|
const TaskContentCard: React.FC<TaskContentCardProps> = ({
|
|
content,
|
|
onUpdate,
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [editedContent, setEditedContent] = useState(content);
|
|
const [contentTab, setContentTab] = useState<'edit' | 'preview'>('edit');
|
|
const contentTextareaRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
useEffect(() => {
|
|
setEditedContent(content);
|
|
}, [content]);
|
|
|
|
useEffect(() => {
|
|
if (isEditing && contentTextareaRef.current) {
|
|
contentTextareaRef.current.focus();
|
|
}
|
|
}, [isEditing]);
|
|
|
|
const handleStartEdit = () => {
|
|
setIsEditing(true);
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
if (editedContent !== content) {
|
|
await onUpdate(editedContent);
|
|
}
|
|
setIsEditing(false);
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setEditedContent(content);
|
|
setIsEditing(false);
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
|
handleSave();
|
|
} else if (e.key === 'Escape') {
|
|
handleCancel();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
{isEditing ? (
|
|
<div className="rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 border-blue-500 dark:border-blue-400 p-6">
|
|
<div className="relative">
|
|
{/* Floating toggle buttons */}
|
|
<div className="absolute top-2 right-2 z-10 flex space-x-1">
|
|
<button
|
|
type="button"
|
|
onClick={() => setContentTab('edit')}
|
|
className={`p-1.5 rounded-md transition-colors ${
|
|
contentTab === 'edit'
|
|
? 'bg-blue-600 text-white'
|
|
: 'bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-700'
|
|
}`}
|
|
title={t('common.edit', 'Edit')}
|
|
>
|
|
<PencilIcon className="h-3 w-3" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setContentTab('preview')}
|
|
className={`p-1.5 rounded-md transition-colors ${
|
|
contentTab === 'preview'
|
|
? 'bg-blue-600 text-white'
|
|
: 'bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-700'
|
|
}`}
|
|
title={t('common.preview', 'Preview')}
|
|
>
|
|
<EyeIcon className="h-3 w-3" />
|
|
</button>
|
|
</div>
|
|
|
|
{contentTab === 'edit' ? (
|
|
<textarea
|
|
ref={contentTextareaRef}
|
|
value={editedContent}
|
|
onChange={(e) =>
|
|
setEditedContent(e.target.value)
|
|
}
|
|
onKeyDown={handleKeyDown}
|
|
className="w-full min-h-[200px] bg-transparent border-none focus:ring-0 focus:outline-none text-gray-900 dark:text-gray-100 resize-y font-normal pr-20"
|
|
placeholder={t(
|
|
'task.contentPlaceholder',
|
|
'Add content here... (Markdown supported)'
|
|
)}
|
|
/>
|
|
) : (
|
|
<div className="w-full min-h-[200px] bg-gray-50 dark:bg-gray-800 rounded p-3 pr-20 overflow-y-auto">
|
|
{editedContent ? (
|
|
<MarkdownRenderer
|
|
content={editedContent}
|
|
className="prose dark:prose-invert max-w-none"
|
|
/>
|
|
) : (
|
|
<p className="text-gray-500 dark:text-gray-400 italic">
|
|
{t(
|
|
'task.noContentPreview',
|
|
'No content to preview. Switch to Edit mode to add content.'
|
|
)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center justify-between mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
{t(
|
|
'task.contentEditHint',
|
|
'Press Cmd/Ctrl+Enter to save, Esc to cancel'
|
|
)}
|
|
</span>
|
|
<div className="flex space-x-2">
|
|
<button
|
|
onClick={handleSave}
|
|
className="px-4 py-2 text-sm bg-green-600 dark:bg-green-500 text-white rounded hover:bg-green-700 dark:hover:bg-green-600 transition-colors"
|
|
>
|
|
{t('common.save', 'Save')}
|
|
</button>
|
|
<button
|
|
onClick={handleCancel}
|
|
className="px-4 py-2 text-sm bg-gray-200 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded hover:bg-gray-300 dark:hover:bg-gray-700 transition-colors"
|
|
>
|
|
{t('common.cancel', 'Cancel')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : content ? (
|
|
<div
|
|
onClick={handleStartEdit}
|
|
className="rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 border-gray-50 dark:border-gray-800 hover:border-gray-200 dark:hover:border-gray-700 p-6 cursor-pointer transition-colors"
|
|
title={t(
|
|
'task.clickToEditContent',
|
|
'Click to edit content'
|
|
)}
|
|
>
|
|
<MarkdownRenderer
|
|
content={content}
|
|
className="prose dark:prose-invert max-w-none"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div
|
|
onClick={handleStartEdit}
|
|
className="rounded-lg shadow-sm bg-white dark:bg-gray-900 border-2 border-gray-50 dark:border-gray-800 hover:border-gray-200 dark:hover:border-gray-700 p-6 cursor-pointer transition-colors"
|
|
title={t('task.clickToAddContent', 'Click to add content')}
|
|
>
|
|
<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', 'Add some content')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TaskContentCard;
|