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
Files changed (1) hide show
  1. client/src/pages/TutorialTasks.tsx +37 -14
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={async () => {
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={async () => {
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"