Spaces:
Sleeping
Sleeping
Tristan Yu commited on
Commit ·
b5ec1e9
1
Parent(s): 189cb24
Reorder UX: stable swap via two position updates; add B/I toolbar to source text edit
Browse files
client/src/pages/TutorialTasks.tsx
CHANGED
|
@@ -60,6 +60,36 @@ const TutorialTasks: React.FC = () => {
|
|
| 60 |
const [tutorialWeek, setTutorialWeek] = useState<TutorialWeek | null>(null);
|
| 61 |
const [userSubmissions, setUserSubmissions] = useState<{[key: string]: UserSubmission[]}>({});
|
| 62 |
const [sourceHeights, setSourceHeights] = useState<{[key: string]: number}>({});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
const [loading, setLoading] = useState(true);
|
| 64 |
const [submitting, setSubmitting] = useState<{[key: string]: boolean}>({});
|
| 65 |
const [translationText, setTranslationText] = useState<{[key: string]: string}>({});
|
|
@@ -1329,24 +1359,12 @@ const TutorialTasks: React.FC = () => {
|
|
| 1329 |
<span className="ml-2 inline-flex items-center space-x-1">
|
| 1330 |
<button
|
| 1331 |
className="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200"
|
| 1332 |
-
onClick={
|
| 1333 |
-
try {
|
| 1334 |
-
const newPos = Math.max(0, (task.position ?? tutorialTasks.indexOf(task)) - 1);
|
| 1335 |
-
await api.put(`/api/auth/admin/tutorial-tasks/${task._id}/position`, { position: newPos });
|
| 1336 |
-
await fetchTutorialTasks(false);
|
| 1337 |
-
} catch (e) { console.error('Reorder up failed', e); }
|
| 1338 |
-
}}
|
| 1339 |
title="Move up"
|
| 1340 |
>↑</button>
|
| 1341 |
<button
|
| 1342 |
className="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200"
|
| 1343 |
-
onClick={
|
| 1344 |
-
try {
|
| 1345 |
-
const newPos = (task.position ?? tutorialTasks.indexOf(task)) + 1;
|
| 1346 |
-
await api.put(`/api/auth/admin/tutorial-tasks/${task._id}/position`, { position: newPos });
|
| 1347 |
-
await fetchTutorialTasks(false);
|
| 1348 |
-
} catch (e) { console.error('Reorder down failed', e); }
|
| 1349 |
-
}}
|
| 1350 |
title="Move down"
|
| 1351 |
>↓</button>
|
| 1352 |
</span>
|
|
@@ -1400,7 +1418,12 @@ const TutorialTasks: React.FC = () => {
|
|
| 1400 |
<div className="bg-gradient-to-r from-indigo-50 to-blue-50 rounded-xl p-6 mb-6 border border-indigo-200">
|
| 1401 |
{editingTask === task._id ? (
|
| 1402 |
<div className="space-y-4">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1403 |
<textarea
|
|
|
|
| 1404 |
value={editForm.content}
|
| 1405 |
onChange={(e) => setEditForm({...editForm, content: e.target.value})}
|
| 1406 |
className="w-full px-4 py-3 border border-indigo-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
|
|
|
| 60 |
const [tutorialWeek, setTutorialWeek] = useState<TutorialWeek | null>(null);
|
| 61 |
const [userSubmissions, setUserSubmissions] = useState<{[key: string]: UserSubmission[]}>({});
|
| 62 |
const [sourceHeights, setSourceHeights] = useState<{[key: string]: number}>({});
|
| 63 |
+
|
| 64 |
+
// Move a task up or down by normalizing positions for the current visible list (weeks 4–6 only)
|
| 65 |
+
const moveTask = async (taskId: string, direction: 'up' | 'down') => {
|
| 66 |
+
try {
|
| 67 |
+
const isAdmin = JSON.parse(localStorage.getItem('user') || '{}').role === 'admin';
|
| 68 |
+
if (!isAdmin || selectedWeek < 4) return;
|
| 69 |
+
// Build ordered list for the current week from what is rendered
|
| 70 |
+
const current = tutorialTasks.filter(t => t.weekNumber === selectedWeek);
|
| 71 |
+
const index = current.findIndex(t => t._id === taskId);
|
| 72 |
+
if (index === -1) return;
|
| 73 |
+
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
| 74 |
+
if (targetIndex < 0 || targetIndex >= current.length) return;
|
| 75 |
+
|
| 76 |
+
// Normalize positions to 0..n-1 based on current screen order
|
| 77 |
+
const normalized = current.map((t, i) => ({ id: t._id, position: i }));
|
| 78 |
+
// Swap the two entries
|
| 79 |
+
const tmp = normalized[index].position;
|
| 80 |
+
normalized[index].position = normalized[targetIndex].position;
|
| 81 |
+
normalized[targetIndex].position = tmp;
|
| 82 |
+
|
| 83 |
+
// Only update the two affected tasks
|
| 84 |
+
const updates = [normalized[index], normalized[targetIndex]];
|
| 85 |
+
for (const u of updates) {
|
| 86 |
+
await api.put(`/api/auth/admin/tutorial-tasks/${u.id}/position`, { position: u.position });
|
| 87 |
+
}
|
| 88 |
+
await fetchTutorialTasks(false);
|
| 89 |
+
} catch (error) {
|
| 90 |
+
console.error('Reorder failed', error);
|
| 91 |
+
}
|
| 92 |
+
};
|
| 93 |
const [loading, setLoading] = useState(true);
|
| 94 |
const [submitting, setSubmitting] = useState<{[key: string]: boolean}>({});
|
| 95 |
const [translationText, setTranslationText] = useState<{[key: string]: string}>({});
|
|
|
|
| 1359 |
<span className="ml-2 inline-flex items-center space-x-1">
|
| 1360 |
<button
|
| 1361 |
className="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200"
|
| 1362 |
+
onClick={() => moveTask(task._id, 'up')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1363 |
title="Move up"
|
| 1364 |
>↑</button>
|
| 1365 |
<button
|
| 1366 |
className="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200"
|
| 1367 |
+
onClick={() => moveTask(task._id, 'down')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1368 |
title="Move down"
|
| 1369 |
>↓</button>
|
| 1370 |
</span>
|
|
|
|
| 1418 |
<div className="bg-gradient-to-r from-indigo-50 to-blue-50 rounded-xl p-6 mb-6 border border-indigo-200">
|
| 1419 |
{editingTask === task._id ? (
|
| 1420 |
<div className="space-y-4">
|
| 1421 |
+
<div className="flex items-center justify-end space-x-2 mb-2">
|
| 1422 |
+
<button onClick={() => applyInlineFormat('tutorial-newtask-input', editForm.content, v => setEditForm({ ...editForm, content: v }), '**')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded">B</button>
|
| 1423 |
+
<button onClick={() => applyInlineFormat('tutorial-newtask-input', editForm.content, v => setEditForm({ ...editForm, content: v }), '*')} className="px-2 py-1 text-xs bg-indigo-100 text-indigo-900 rounded italic">I</button>
|
| 1424 |
+
</div>
|
| 1425 |
<textarea
|
| 1426 |
+
id="tutorial-newtask-input"
|
| 1427 |
value={editForm.content}
|
| 1428 |
onChange={(e) => setEditForm({...editForm, content: e.target.value})}
|
| 1429 |
className="w-full px-4 py-3 border border-indigo-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|