Tristan Yu commited on
Commit
03a1f85
Β·
1 Parent(s): 99c1fa1

Add subtitle submissions viewing feature - new UI panel below video player

Browse files
client/src/pages/WeeklyPractice.tsx CHANGED
@@ -68,6 +68,16 @@ interface VideoInfo {
68
  currentSegment: number;
69
  }
70
 
 
 
 
 
 
 
 
 
 
 
71
  const WeeklyPractice: React.FC = () => {
72
  const [selectedWeek, setSelectedWeek] = useState<number>(() => {
73
  const saved = localStorage.getItem('selectedWeeklyPracticeWeek');
@@ -114,6 +124,12 @@ const WeeklyPractice: React.FC = () => {
114
  const [editingSegment, setEditingSegment] = useState<number | null>(null);
115
  const [currentVideoTime, setCurrentVideoTime] = useState<number>(0);
116
  const [currentDisplayedSubtitle, setCurrentDisplayedSubtitle] = useState<string>('');
 
 
 
 
 
 
117
 
118
  const weeks = [1, 2, 3, 4, 5, 6];
119
 
@@ -155,6 +171,11 @@ const WeeklyPractice: React.FC = () => {
155
  video.addEventListener('timeupdate', checkEndTime);
156
  }
157
  }
 
 
 
 
 
158
  };
159
 
160
  const parseTimeToSeconds = (timeString: string): number => {
@@ -320,6 +341,27 @@ const WeeklyPractice: React.FC = () => {
320
  setTimeout(() => setSaveStatus('Save'), 2000);
321
  }
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  // Clear the input field after saving
324
  setTargetText('');
325
  setCharacterCount(0);
@@ -453,6 +495,44 @@ const WeeklyPractice: React.FC = () => {
453
  }
454
  };
455
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  // Load subtitles when component mounts
457
  React.useEffect(() => {
458
  if (selectedWeek === 2) {
@@ -1284,6 +1364,98 @@ const WeeklyPractice: React.FC = () => {
1284
  </div>
1285
  </div>
1286
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1287
  </div>
1288
  ) : (
1289
  /* Weekly Practice */
 
68
  currentSegment: number;
69
  }
70
 
71
+ interface SubtitleSubmission {
72
+ _id: string;
73
+ username: string;
74
+ chineseTranslation: string;
75
+ submissionDate: string;
76
+ isAnonymous: boolean;
77
+ status?: string;
78
+ notes?: string;
79
+ }
80
+
81
  const WeeklyPractice: React.FC = () => {
82
  const [selectedWeek, setSelectedWeek] = useState<number>(() => {
83
  const saved = localStorage.getItem('selectedWeeklyPracticeWeek');
 
124
  const [editingSegment, setEditingSegment] = useState<number | null>(null);
125
  const [currentVideoTime, setCurrentVideoTime] = useState<number>(0);
126
  const [currentDisplayedSubtitle, setCurrentDisplayedSubtitle] = useState<string>('');
127
+
128
+ // Submissions viewing state
129
+ const [showSubmissions, setShowSubmissions] = useState(false);
130
+ const [submissions, setSubmissions] = useState<SubtitleSubmission[]>([]);
131
+ const [loadingSubmissions, setLoadingSubmissions] = useState(false);
132
+ const [submissionCount, setSubmissionCount] = useState(0);
133
 
134
  const weeks = [1, 2, 3, 4, 5, 6];
135
 
 
171
  video.addEventListener('timeupdate', checkEndTime);
172
  }
173
  }
174
+
175
+ // Fetch submissions for this segment if submissions are shown
176
+ if (showSubmissions) {
177
+ fetchSubmissionsForSegment(segmentId);
178
+ }
179
  };
180
 
181
  const parseTimeToSeconds = (timeString: string): number => {
 
341
  setTimeout(() => setSaveStatus('Save'), 2000);
342
  }
343
 
344
+ // Also save as a submission
345
+ try {
346
+ const user = JSON.parse(localStorage.getItem('user') || '{}');
347
+ const submissionResponse = await api.post('/api/subtitle-submissions/submit', {
348
+ segmentId: currentSegment,
349
+ chineseTranslation: targetText,
350
+ isAnonymous: false,
351
+ weekNumber: 2
352
+ });
353
+
354
+ if (submissionResponse.data.success) {
355
+ console.log('βœ… Translation submitted successfully');
356
+ // Refresh submissions if they're currently shown
357
+ if (showSubmissions) {
358
+ fetchSubmissionsForSegment(currentSegment);
359
+ }
360
+ }
361
+ } catch (submissionError: any) {
362
+ console.warn('⚠️ Submission save failed:', submissionError.message);
363
+ }
364
+
365
  // Clear the input field after saving
366
  setTargetText('');
367
  setCharacterCount(0);
 
495
  }
496
  };
497
 
498
+ // Fetch submissions for a specific segment
499
+ const fetchSubmissionsForSegment = async (segmentId: number) => {
500
+ try {
501
+ setLoadingSubmissions(true);
502
+ console.log('πŸ”§ Fetching submissions for segment:', segmentId);
503
+
504
+ const response = await api.get(`/api/subtitle-submissions/segment/${segmentId}?week=2`);
505
+
506
+ if (response.data.success) {
507
+ setSubmissions(response.data.data);
508
+ setSubmissionCount(response.data.count);
509
+ console.log('βœ… Fetched submissions:', response.data.count);
510
+ } else {
511
+ console.error('❌ Failed to fetch submissions:', response.data);
512
+ setSubmissions([]);
513
+ setSubmissionCount(0);
514
+ }
515
+ } catch (error) {
516
+ console.error('❌ Error fetching submissions:', error);
517
+ setSubmissions([]);
518
+ setSubmissionCount(0);
519
+ } finally {
520
+ setLoadingSubmissions(false);
521
+ }
522
+ };
523
+
524
+ // Toggle submissions panel
525
+ const toggleSubmissions = async () => {
526
+ if (!showSubmissions) {
527
+ // Show submissions and fetch data
528
+ setShowSubmissions(true);
529
+ await fetchSubmissionsForSegment(currentSegment);
530
+ } else {
531
+ // Hide submissions
532
+ setShowSubmissions(false);
533
+ }
534
+ };
535
+
536
  // Load subtitles when component mounts
537
  React.useEffect(() => {
538
  if (selectedWeek === 2) {
 
1364
  </div>
1365
  </div>
1366
  </div>
1367
+
1368
+ {/* Submissions Panel - Only show for Week 2 subtitling */}
1369
+ {selectedWeek === 2 && (
1370
+ <div className="mt-6">
1371
+ <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
1372
+ <div className="flex items-center justify-between mb-4">
1373
+ <h3 className="text-lg font-semibold text-gray-900">
1374
+ πŸ‘₯ View Submissions for Segment {currentSegment}
1375
+ </h3>
1376
+ <button
1377
+ onClick={toggleSubmissions}
1378
+ className={`px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors duration-200 ${
1379
+ showSubmissions
1380
+ ? 'bg-gray-500 hover:bg-gray-600 text-white'
1381
+ : 'bg-purple-600 hover:bg-purple-700 text-white'
1382
+ }`}
1383
+ >
1384
+ <span>{showSubmissions ? 'Hide Submissions' : 'Show Submissions'}</span>
1385
+ <span>{showSubmissions ? 'β–Ό' : 'β–Ά'}</span>
1386
+ </button>
1387
+ </div>
1388
+
1389
+ {showSubmissions && (
1390
+ <div className="space-y-4">
1391
+ {loadingSubmissions ? (
1392
+ <div className="text-center py-8">
1393
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto mb-4"></div>
1394
+ <p className="text-gray-600">Loading submissions...</p>
1395
+ </div>
1396
+ ) : submissions.length > 0 ? (
1397
+ <>
1398
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1399
+ {submissions.map((submission) => (
1400
+ <div key={submission._id} className="bg-gray-50 rounded-lg p-4 border border-gray-200">
1401
+ <div className="flex items-center justify-between mb-2">
1402
+ <span className="font-medium text-gray-900">
1403
+ {submission.username}
1404
+ </span>
1405
+ <span className="text-xs text-gray-500">
1406
+ {new Date(submission.submissionDate).toLocaleString()}
1407
+ </span>
1408
+ </div>
1409
+ <div className="bg-white rounded p-3 border border-gray-200">
1410
+ <p className="text-gray-800 text-sm leading-relaxed">
1411
+ {submission.chineseTranslation}
1412
+ </p>
1413
+ </div>
1414
+ {submission.status && (
1415
+ <div className="mt-2">
1416
+ <span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
1417
+ submission.status === 'approved' ? 'bg-green-100 text-green-800' :
1418
+ submission.status === 'reviewed' ? 'bg-blue-100 text-blue-800' :
1419
+ 'bg-gray-100 text-gray-800'
1420
+ }`}>
1421
+ {submission.status}
1422
+ </span>
1423
+ </div>
1424
+ )}
1425
+ </div>
1426
+ ))}
1427
+ </div>
1428
+ <div className="flex justify-between items-center pt-4 border-t border-gray-200">
1429
+ <span className="text-sm text-gray-600">
1430
+ {submissionCount} submission{submissionCount !== 1 ? 's' : ''} for this segment
1431
+ </span>
1432
+ <button
1433
+ onClick={() => {
1434
+ // Export functionality can be added here
1435
+ console.log('Export submissions for segment', currentSegment);
1436
+ }}
1437
+ className="px-3 py-1 text-sm bg-purple-100 text-purple-700 rounded hover:bg-purple-200 transition-colors"
1438
+ >
1439
+ Export This Segment
1440
+ </button>
1441
+ </div>
1442
+ </>
1443
+ ) : (
1444
+ <div className="text-center py-8">
1445
+ <div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
1446
+ <span className="text-2xl">πŸ“</span>
1447
+ </div>
1448
+ <h4 className="text-lg font-medium text-gray-900 mb-2">No submissions yet</h4>
1449
+ <p className="text-gray-600">
1450
+ Be the first to submit a translation for this segment!
1451
+ </p>
1452
+ </div>
1453
+ )}
1454
+ </div>
1455
+ )}
1456
+ </div>
1457
+ </div>
1458
+ )}
1459
  </div>
1460
  ) : (
1461
  /* Weekly Practice */
client/src/services/api.ts CHANGED
@@ -19,6 +19,7 @@ console.log('Environment variables:', {
19
  });
20
  console.log('Build timestamp:', new Date().toISOString()); // FORCE REBUILD - Video seeking and subtitle syncing
21
  console.log('πŸ”„ FORCE REBUILD: Admin API routes fixed - should resolve 404 errors');
 
22
 
23
  // Request interceptor to add auth token and user role
24
  api.interceptors.request.use(
@@ -28,12 +29,17 @@ api.interceptors.request.use(
28
  config.headers.Authorization = `Bearer ${token}`;
29
  }
30
 
31
- // Add user role to headers
32
  const user = localStorage.getItem('user');
33
  if (user) {
34
  try {
35
  const userData = JSON.parse(user);
36
  config.headers['user-role'] = userData.role || 'visitor';
 
 
 
 
 
37
  } catch (error) {
38
  config.headers['user-role'] = 'visitor';
39
  }
 
19
  });
20
  console.log('Build timestamp:', new Date().toISOString()); // FORCE REBUILD - Video seeking and subtitle syncing
21
  console.log('πŸ”„ FORCE REBUILD: Admin API routes fixed - should resolve 404 errors');
22
+ console.log('πŸ”„ FORCE REBUILD: Subtitle submissions feature added - new UI and API endpoints');
23
 
24
  // Request interceptor to add auth token and user role
25
  api.interceptors.request.use(
 
29
  config.headers.Authorization = `Bearer ${token}`;
30
  }
31
 
32
+ // Add user role and info to headers
33
  const user = localStorage.getItem('user');
34
  if (user) {
35
  try {
36
  const userData = JSON.parse(user);
37
  config.headers['user-role'] = userData.role || 'visitor';
38
+ config.headers['user-info'] = JSON.stringify({
39
+ _id: userData._id,
40
+ username: userData.username,
41
+ role: userData.role
42
+ });
43
  } catch (error) {
44
  config.headers['user-role'] = 'visitor';
45
  }