linguabot commited on
Commit
ff65064
·
verified ·
1 Parent(s): 7a5ce86

Upload folder using huggingface_hub

Browse files
client/src/components/SubtitlingModule.tsx ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ export interface SubtitleSegment {
4
+ id: number;
5
+ startTime: string;
6
+ endTime: string;
7
+ duration: string;
8
+ sourceText: string;
9
+ targetText?: string;
10
+ isCurrent?: boolean;
11
+ }
12
+
13
+ export interface VideoInfo {
14
+ title: string;
15
+ duration: string;
16
+ totalSegments: number;
17
+ currentSegment: number;
18
+ }
19
+
20
+ interface SubtitlingModuleProps {
21
+ videoInfo: VideoInfo;
22
+ subtitleSegments: SubtitleSegment[];
23
+ currentDisplayedSubtitle?: string;
24
+ onSegmentClick?: (id: number) => void;
25
+ getSegmentButtonClass?: (id: number) => string;
26
+ onPlayPause?: () => void;
27
+ isPlaying?: boolean;
28
+ onSaveSegment?: (id: number, text: string) => void;
29
+ }
30
+
31
+ /**
32
+ * Reusable subtitling interface template for future weeks.
33
+ * Not currently rendered in any page; kept for reuse.
34
+ */
35
+ const SubtitlingModule: React.FC<SubtitlingModuleProps> = ({
36
+ videoInfo,
37
+ subtitleSegments,
38
+ currentDisplayedSubtitle,
39
+ onSegmentClick,
40
+ getSegmentButtonClass,
41
+ onPlayPause,
42
+ isPlaying,
43
+ }) => {
44
+ return (
45
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
46
+ <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
47
+ <div className="mb-4">
48
+ <h3 className="text-xl font-bold text-gray-900 mb-2">{videoInfo.title}</h3>
49
+ </div>
50
+ <div className="bg-black rounded-lg aspect-video overflow-hidden relative">
51
+ <div className="absolute inset-0 flex items-center justify-center text-white/70 text-sm">Video Placeholder</div>
52
+ {currentDisplayedSubtitle && (
53
+ <div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10">
54
+ <div className="bg-black bg-opacity-80 text-white px-6 py-3 rounded-lg text-center max-w-lg">
55
+ <p className={`text-base font-medium leading-relaxed tracking-wide ${currentDisplayedSubtitle.length <= 42 ? 'whitespace-nowrap' : 'whitespace-pre-line'}`}>
56
+ {currentDisplayedSubtitle}
57
+ </p>
58
+ </div>
59
+ </div>
60
+ )}
61
+ </div>
62
+ </div>
63
+
64
+ <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
65
+ <h3 className="text-xl font-bold text-gray-900 mb-4">Translation Workspace</h3>
66
+ <div className="grid grid-cols-9 gap-1 mb-6">
67
+ {subtitleSegments.map((segment) => (
68
+ <button
69
+ key={segment.id}
70
+ onClick={() => onSegmentClick && onSegmentClick(segment.id)}
71
+ className={`px-1 py-1 rounded text-xs w-full ${getSegmentButtonClass ? getSegmentButtonClass(segment.id) : ''}`}
72
+ >
73
+ {segment.id}
74
+ </button>
75
+ ))}
76
+ </div>
77
+ <div className="flex items-center gap-2 mb-4">
78
+ <button onClick={onPlayPause} className="px-3 py-1.5 text-sm rounded-md border border-gray-300">
79
+ {isPlaying ? 'Pause' : 'Play'}
80
+ </button>
81
+ <div className="text-sm text-gray-600">{videoInfo.currentSegment}/{videoInfo.totalSegments}</div>
82
+ </div>
83
+ <div className="space-y-4">
84
+ {subtitleSegments.map(seg => (
85
+ <div key={seg.id} className="border border-gray-200 rounded-lg p-3">
86
+ <div className="text-xs text-gray-500 mb-1">{seg.startTime} → {seg.endTime} ({seg.duration})</div>
87
+ <div className="text-sm text-gray-800 mb-2">{seg.sourceText}</div>
88
+ <textarea className="w-full border border-gray-300 rounded p-2 text-sm" defaultValue={seg.targetText || ''} rows={2} />
89
+ </div>
90
+ ))}
91
+ </div>
92
+ </div>
93
+ </div>
94
+ );
95
+ };
96
+
97
+ export default SubtitlingModule;
98
+
99
+
client/src/pages/WeeklyPractice.tsx CHANGED
@@ -1746,287 +1746,292 @@ const WeeklyPractice: React.FC = () => {
1746
 
1747
  {/* Special Subtitling Interface for Week 2 (hidden from students when week is hidden) */}
1748
  {!isWeekTransitioning && selectedWeek === 2 && (isAdmin || !isWeekHidden) ? (
1749
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
1750
- {/* Left Panel - Video Player */}
1751
- <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
1752
- <div className="mb-4">
1753
- <h3 className="text-xl font-bold text-gray-900 mb-2">{videoInfo.title}</h3>
1754
- </div>
1755
-
1756
- {/* Video Player */}
1757
- <div className="bg-black rounded-lg aspect-video overflow-hidden relative">
1758
- <video
1759
- className="w-full h-full"
1760
- controls
1761
- preload="metadata"
1762
- id="nike-video"
1763
- onError={(e) => console.error('Video loading error:', e)}
1764
- onLoadStart={() => console.log('Video loading started')}
1765
- onCanPlay={() => console.log('Video can play')}
1766
- >
1767
- <source src="https://huggingface.co/spaces/linguabot/transcreation-frontend/resolve/main/public/videos/nike-winning-isnt-for-everyone.mp4" type="video/mp4" />
1768
- Your browser does not support the video tag.
1769
- </video>
1770
-
1771
- {/* On-screen subtitles */}
1772
- {currentDisplayedSubtitle && (
1773
- <div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10">
1774
- <div className="bg-black bg-opacity-80 text-white px-6 py-3 rounded-lg text-center max-w-lg">
1775
- <p className={`text-base font-medium leading-relaxed tracking-wide ${currentDisplayedSubtitle.length <= 42 ? 'whitespace-nowrap' : 'whitespace-pre-line'}`} style={{ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
1776
- {formatSubtitleForDisplay(currentDisplayedSubtitle)}
1777
- </p>
1778
- </div>
1779
- </div>
1780
- )}
1781
  </div>
1782
- </div>
1783
-
1784
- {/* Right Panel - Translation Workspace */}
1785
- <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
1786
- <h3 className="text-xl font-bold text-gray-900 mb-4">Translation Workspace</h3>
1787
-
1788
- {/* Segment Navigation Grid */}
1789
- {loadingSubtitles ? (
1790
- <div className="grid grid-cols-9 gap-1 mb-6">
1791
- {Array.from({ length: 26 }, (_, i) => (
1792
- <div key={i} className="px-1 py-1 rounded text-xs w-full bg-gray-200 animate-pulse">
1793
- {i + 1}
1794
- </div>
1795
- ))}
1796
- </div>
1797
- ) : (
1798
- <div className="grid grid-cols-9 gap-1 mb-6">
1799
- {subtitleSegments.map((segment) => (
1800
- <button
1801
- key={segment.id}
1802
- onClick={() => handleSegmentClick(segment.id)}
1803
- className={`px-1 py-1 rounded text-xs w-full ${getSegmentButtonClass(segment.id)}`}
1804
- >
1805
- {segment.id}
1806
- </button>
1807
- ))}
1808
- </div>
1809
- )}
1810
-
1811
- {/* Admin Time Code Editor */}
1812
- {editingSegment && (((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin')) && (
1813
- <div className="bg-indigo-50 border border-indigo-200 rounded-lg p-4 mb-4">
1814
- <h4 className="text-sm font-medium text-indigo-800 mb-2">Edit Time Codes for Segment {editingSegment}</h4>
1815
- <div className="grid grid-cols-2 gap-2">
1816
- <div>
1817
- <label className="block text-xs text-indigo-700 mb-1">Start Time</label>
1818
- <input
1819
- type="text"
1820
- id="startTimeInput"
1821
- defaultValue={subtitleSegments[editingSegment - 1]?.startTime}
1822
- className="w-full px-2 py-1 text-xs border border-indigo-300 rounded"
1823
- placeholder="00:00:00,000"
1824
- />
1825
- </div>
1826
- <div>
1827
- <label className="block text-xs text-indigo-700 mb-1">End Time</label>
1828
- <input
1829
- type="text"
1830
- id="endTimeInput"
1831
- defaultValue={subtitleSegments[editingSegment - 1]?.endTime}
1832
- className="w-full px-2 py-1 text-xs border border-indigo-300 rounded"
1833
- placeholder="00:00:00,000"
1834
- />
1835
- </div>
1836
- </div>
1837
- <div className="flex space-x-2 mt-2">
1838
- <button
1839
- onClick={() => setEditingSegment(null)}
1840
- className="px-2 py-1 text-xs bg-gray-500 text-white rounded"
1841
- >
1842
- Cancel
1843
- </button>
1844
- <button
1845
- onClick={() => {
1846
- const startInput = document.getElementById('startTimeInput') as HTMLInputElement;
1847
- const endInput = document.getElementById('endTimeInput') as HTMLInputElement;
1848
- if (startInput && endInput && startInput.value && endInput.value) {
1849
- handleSaveTimeCode(editingSegment, startInput.value, endInput.value);
1850
- }
1851
- }}
1852
- className="px-2 py-1 text-xs bg-indigo-600 text-white rounded"
1853
- >
1854
- Save
1855
- </button>
1856
- </div>
1857
- </div>
1858
- )}
1859
-
1860
- {/* Current Segment Details */}
1861
- <div className="bg-gray-50 rounded-lg p-4 mb-4 border border-gray-200">
1862
- <div className="flex items-center justify-between">
1863
- <p className="text-gray-800">
1864
- Segment {currentSegment}: {subtitleSegments[currentSegment - 1]?.startTime} – {subtitleSegments[currentSegment - 1]?.endTime} ({subtitleSegments[currentSegment - 1]?.duration})
1865
- </p>
1866
- {((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin') && (
1867
- <button
1868
- onClick={() => handleEditTimeCode(currentSegment)}
1869
- className="bg-indigo-600 hover:bg-indigo-700 text-white px-2 py-1 rounded text-xs flex items-center space-x-1"
1870
- title="Edit time codes"
1871
- >
1872
- <PencilIcon className="w-3 h-3" />
1873
- <span>Edit</span>
1874
- </button>
1875
- )}
1876
- </div>
1877
- </div>
1878
-
1879
- {/* Source Text */}
1880
- <div className="mb-4">
1881
- <label className="block text-sm font-medium text-gray-700 mb-2">Source Text</label>
1882
- <textarea
1883
- value={subtitleText}
1884
- readOnly
1885
- className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-800"
1886
- rows={2}
1887
- />
1888
- </div>
1889
-
1890
- {/* Target Text */}
1891
- <div className="mb-4">
1892
- <label className="block text-sm font-medium text-gray-700 mb-2">Target Text</label>
1893
- <textarea
1894
- value={targetText}
1895
- onChange={(e) => handleTargetTextChange(e.target.value)}
1896
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
1897
- rows={2}
1898
- placeholder="Enter your translation..."
1899
- />
1900
- <div className="flex justify-between items-center mt-2">
1901
- <span className="text-xs text-gray-600">Recommended: 2 lines max, 16 chars/line (Netflix standard)</span>
1902
- <span className="text-xs text-gray-600">{characterCount} characters</span>
1903
- </div>
1904
- </div>
1905
-
1906
- {/* Action Buttons */}
1907
- <div className="flex space-x-3 mb-4">
1908
- <button
1909
- onClick={handleSaveTranslation}
1910
- className={`px-4 py-2 rounded-lg ${
1911
- saveStatus === 'Saved!' ? 'bg-green-600 hover:bg-green-700' :
1912
- saveStatus === 'Saved Locally' ? 'bg-yellow-600 hover:bg-yellow-700' :
1913
- saveStatus === 'Error' ? 'bg-red-600 hover:bg-red-700' :
1914
- 'bg-indigo-600 hover:bg-indigo-700'
1915
- } text-white`}
1916
- >
1917
- {saveStatus}
1918
- </button>
1919
- <button
1920
- onClick={handlePreviewTranslation}
1921
- className={`px-4 py-2 rounded-lg flex items-center space-x-2 ${
1922
- showTranslatedSubtitles
1923
- ? 'bg-green-500 hover:bg-green-600 text-white'
1924
- : 'bg-gray-500 hover:bg-gray-600 text-white'
1925
- }`}
1926
- >
1927
- <span>{showTranslatedSubtitles ? 'Show Original' : 'Show Translation'}</span>
1928
- </button>
1929
- </div>
1930
-
1931
- {/* Translation Progress */}
1932
- <div className="mb-4">
1933
- <div className="flex justify-between items-center mb-2">
1934
- <span className="text-sm font-medium text-gray-700">Translation Progress</span>
1935
- <span className="text-sm text-gray-600">{Object.keys(subtitleTranslations).length} of {subtitleSegments.length} segments completed</span>
1936
- </div>
1937
- <div className="w-full bg-gray-200 rounded-full h-2">
1938
- <div
1939
- className="bg-green-500 h-2 rounded-full transition-all duration-300"
1940
- style={{ width: `${(Object.keys(subtitleTranslations).length / subtitleSegments.length) * 100}%` }}
1941
- ></div>
1942
- </div>
1943
- </div>
1944
- </div>
1945
-
1946
- {/* Submissions Panel - Only show for Week 2 subtitling */}
1947
- {selectedWeek === 2 && (
1948
- <div className="mt-6 col-span-2">
1949
- <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
1950
- <div className="flex items-center justify-between mb-4">
1951
- <h3 className="text-lg font-semibold text-gray-900">
1952
- 👥 View Submissions for Segment {currentSegment}
1953
- </h3>
1954
- <button
1955
- onClick={toggleSubmissions}
1956
- className={`px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors duration-200 ${
1957
- showSubmissions
1958
- ? 'bg-gray-500 hover:bg-gray-600 text-white'
1959
- : 'bg-purple-600 hover:bg-purple-700 text-white'
1960
- }`}
1961
- >
1962
- <span>{showSubmissions ? 'Hide Submissions' : 'Show Submissions'}</span>
1963
- <span>{showSubmissions ? '▼' : '▶'}</span>
1964
- </button>
1965
- </div>
1966
-
1967
- {showSubmissions && (
1968
- <div className="space-y-4">
1969
- {loadingSubmissions ? (
1970
- <div className="text-center py-8">
1971
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto mb-4"></div>
1972
- <p className="text-gray-600">Loading submissions...</p>
1973
- </div>
1974
- ) : submissions.length > 0 ? (
1975
- <>
1976
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
1977
- {submissions.map((submission) => (
1978
- <div key={submission._id} className="bg-gray-50 rounded-lg p-4 border border-gray-200">
1979
- <div className="flex items-center justify-between mb-2">
1980
- <span className="font-medium text-gray-900">
1981
- {submission.username}
1982
- </span>
1983
- <div className="flex items-center space-x-2">
1984
- <span className="text-xs text-gray-500">
1985
- {new Date(submission.submissionDate).toLocaleString()}
1986
- </span>
1987
- {((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin') && (
1988
- <button
1989
- onClick={() => handleDeleteSubtitleSubmission(submission._id)}
1990
- className="text-red-500 hover:text-red-700 text-xs"
1991
- title="Delete submission"
1992
- data-submission-id={submission._id}
1993
- >
1994
- 🗑️
1995
- </button>
1996
- )}
1997
- </div>
1998
- </div>
1999
- <div className="bg-white rounded p-3 border border-gray-200">
2000
- <p className="text-gray-800 text-sm leading-relaxed">
2001
- {submission.chineseTranslation}
2002
- </p>
2003
- </div>
2004
- </div>
2005
- ))}
2006
- </div>
2007
- <div className="flex justify-between items-center pt-4 border-t border-gray-200">
2008
- <span className="text-sm text-gray-600">
2009
- {submissionCount} submission{submissionCount !== 1 ? 's' : ''} for this segment
2010
- </span>
2011
- </div>
2012
- </>
2013
- ) : (
2014
- <div className="text-center py-8">
2015
- <div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
2016
- <span className="text-2xl">📝</span>
2017
- </div>
2018
- <h4 className="text-lg font-medium text-gray-900 mb-2">No submissions yet</h4>
2019
- <p className="text-gray-600">
2020
- Be the first to submit a translation for this segment!
2021
- </p>
2022
- </div>
2023
- )}
2024
- </div>
2025
- )}
2026
- </div>
2027
  </div>
 
 
2028
  )}
2029
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2030
  ) : (
2031
  /* Weekly Practice */
2032
  <div className="space-y-6">
 
1746
 
1747
  {/* Special Subtitling Interface for Week 2 (hidden from students when week is hidden) */}
1748
  {!isWeekTransitioning && selectedWeek === 2 && (isAdmin || !isWeekHidden) ? (
1749
+ <div className="bg-white rounded-xl shadow p-6 mb-8">
1750
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">Week 2 Practice</h3>
1751
+ {weeklyPracticeWeek?.translationBrief ? (
1752
+ <div className="bg-ui-panel rounded-xl p-6 mb-6 border border-ui-border">
1753
+ <h4 className="text-ui-text font-semibold text-base mb-2">Translation Brief</h4>
1754
+ <div className="text-ui-text leading-relaxed whitespace-pre-wrap">{renderFormatted(weeklyPracticeWeek.translationBrief)}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1755
  </div>
1756
+ ) : null}
1757
+ {weeklyPractice.length > 0 ? weeklyPractice.map((practice) => (
1758
+ <div key={practice._id} className="bg-white rounded-lg border border-gray-200 p-4 mb-4">
1759
+ <div className="text-gray-900 whitespace-pre-wrap mb-2">{renderFormatted(practice.content)}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1760
  </div>
1761
+ )) : (
1762
+ <div className="text-gray-600">No practice items for Week 2 yet.</div>
1763
  )}
1764
  </div>
1765
+ ) : null}
1766
+ {false && (
1767
+ <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
1768
+ <div className="mb-4">
1769
+ <h3 className="text-xl font-bold text-gray-900 mb-2">{videoInfo.title}</h3>
1770
+ </div>
1771
+
1772
+ {/* Video Player */}
1773
+ <div className="bg-black rounded-lg aspect-video overflow-hidden relative">
1774
+ <video
1775
+ className="w-full h-full"
1776
+ controls
1777
+ preload="metadata"
1778
+ id="nike-video"
1779
+ onError={(e) => console.error('Video loading error:', e)}
1780
+ onLoadStart={() => console.log('Video loading started')}
1781
+ onCanPlay={() => console.log('Video can play')}
1782
+ >
1783
+ <source src="https://huggingface.co/spaces/linguabot/transcreation-frontend/resolve/main/public/videos/nike-winning-isnt-for-everyone.mp4" type="video/mp4" />
1784
+ Your browser does not support the video tag.
1785
+ </video>
1786
+
1787
+ {/* On-screen subtitles */}
1788
+ {currentDisplayedSubtitle && (
1789
+ <div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10">
1790
+ <div className="bg-black bg-opacity-80 text-white px-6 py-3 rounded-lg text-center max-w-lg">
1791
+ <p className={`text-base font-medium leading-relaxed tracking-wide ${currentDisplayedSubtitle.length <= 42 ? 'whitespace-nowrap' : 'whitespace-pre-line'}`} style={{ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
1792
+ {formatSubtitleForDisplay(currentDisplayedSubtitle)}
1793
+ </p>
1794
+ </div>
1795
+ </div>
1796
+ )}
1797
+ </div>
1798
+ </div>
1799
+
1800
+ {/* Right Panel - Translation Workspace */}
1801
+ <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
1802
+ <h3 className="text-xl font-bold text-gray-900 mb-4">Translation Workspace</h3>
1803
+
1804
+ {/* Segment Navigation Grid */}
1805
+ {loadingSubtitles ? (
1806
+ <div className="grid grid-cols-9 gap-1 mb-6">
1807
+ {Array.from({ length: 26 }, (_, i) => (
1808
+ <div key={i} className="px-1 py-1 rounded text-xs w-full bg-gray-200 animate-pulse">
1809
+ {i + 1}
1810
+ </div>
1811
+ ))}
1812
+ </div>
1813
+ ) : (
1814
+ <div className="grid grid-cols-9 gap-1 mb-6">
1815
+ {subtitleSegments.map((segment) => (
1816
+ <button
1817
+ key={segment.id}
1818
+ onClick={() => handleSegmentClick(segment.id)}
1819
+ className={`px-1 py-1 rounded text-xs w-full ${getSegmentButtonClass(segment.id)}`}
1820
+ >
1821
+ {segment.id}
1822
+ </button>
1823
+ ))}
1824
+ </div>
1825
+ )}
1826
+
1827
+ {/* Admin Time Code Editor */}
1828
+ {editingSegment && (((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin')) && (
1829
+ <div className="bg-indigo-50 border border-indigo-200 rounded-lg p-4 mb-4">
1830
+ <h4 className="text-sm font-medium text-indigo-800 mb-2">Edit Time Codes for Segment {editingSegment}</h4>
1831
+ <div className="grid grid-cols-2 gap-2">
1832
+ <div>
1833
+ <label className="block text-xs text-indigo-700 mb-1">Start Time</label>
1834
+ <input
1835
+ type="text"
1836
+ id="startTimeInput"
1837
+ defaultValue={subtitleSegments[editingSegment - 1]?.startTime}
1838
+ className="w-full px-2 py-1 text-xs border border-indigo-300 rounded"
1839
+ placeholder="00:00:00,000"
1840
+ />
1841
+ </div>
1842
+ <div>
1843
+ <label className="block text-xs text-indigo-700 mb-1">End Time</label>
1844
+ <input
1845
+ type="text"
1846
+ id="endTimeInput"
1847
+ defaultValue={subtitleSegments[editingSegment - 1]?.endTime}
1848
+ className="w-full px-2 py-1 text-xs border border-indigo-300 rounded"
1849
+ placeholder="00:00:00,000"
1850
+ />
1851
+ </div>
1852
+ </div>
1853
+ <div className="flex space-x-2 mt-2">
1854
+ <button
1855
+ onClick={() => setEditingSegment(null)}
1856
+ className="px-2 py-1 text-xs bg-gray-500 text-white rounded"
1857
+ >
1858
+ Cancel
1859
+ </button>
1860
+ <button
1861
+ onClick={() => {
1862
+ const startInput = document.getElementById('startTimeInput') as HTMLInputElement;
1863
+ const endInput = document.getElementById('endTimeInput') as HTMLInputElement;
1864
+ if (startInput && endInput && startInput.value && endInput.value) {
1865
+ handleSaveTimeCode(editingSegment, startInput.value, endInput.value);
1866
+ }
1867
+ }}
1868
+ className="px-2 py-1 text-xs bg-indigo-600 text-white rounded"
1869
+ >
1870
+ Save
1871
+ </button>
1872
+ </div>
1873
+ </div>
1874
+ )}
1875
+
1876
+ {/* Current Segment Details */}
1877
+ <div className="bg-gray-50 rounded-lg p-4 mb-4 border border-gray-200">
1878
+ <div className="flex items-center justify-between">
1879
+ <p className="text-gray-800">
1880
+ Segment {currentSegment}: {subtitleSegments[currentSegment - 1]?.startTime} – {subtitleSegments[currentSegment - 1]?.endTime} ({subtitleSegments[currentSegment - 1]?.duration})
1881
+ </p>
1882
+ {((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin') && (
1883
+ <button
1884
+ onClick={() => handleEditTimeCode(currentSegment)}
1885
+ className="bg-indigo-600 hover:bg-indigo-700 text-white px-2 py-1 rounded text-xs flex items-center space-x-1"
1886
+ title="Edit time codes"
1887
+ >
1888
+ <PencilIcon className="w-3 h-3" />
1889
+ <span>Edit</span>
1890
+ </button>
1891
+ )}
1892
+ </div>
1893
+ </div>
1894
+
1895
+ {/* Source Text */}
1896
+ <div className="mb-4">
1897
+ <label className="block text-sm font-medium text-gray-700 mb-2">Source Text</label>
1898
+ <textarea
1899
+ value={subtitleText}
1900
+ readOnly
1901
+ className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-800"
1902
+ rows={2}
1903
+ />
1904
+ </div>
1905
+
1906
+ {/* Target Text */}
1907
+ <div className="mb-4">
1908
+ <label className="block text-sm font-medium text-gray-700 mb-2">Target Text</label>
1909
+ <textarea
1910
+ value={targetText}
1911
+ onChange={(e) => handleTargetTextChange(e.target.value)}
1912
+ className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
1913
+ rows={2}
1914
+ placeholder="Enter your translation..."
1915
+ />
1916
+ <div className="flex justify-between items-center mt-2">
1917
+ <span className="text-xs text-gray-600">Recommended: 2 lines max, 16 chars/line (Netflix standard)</span>
1918
+ <span className="text-xs text-gray-600">{characterCount} characters</span>
1919
+ </div>
1920
+ </div>
1921
+
1922
+ {/* Action Buttons */}
1923
+ <div className="flex space-x-3 mb-4">
1924
+ <button
1925
+ onClick={handleSaveTranslation}
1926
+ className={`px-4 py-2 rounded-lg ${
1927
+ saveStatus === 'Saved!' ? 'bg-green-600 hover:bg-green-700' :
1928
+ saveStatus === 'Saved Locally' ? 'bg-yellow-600 hover:bg-yellow-700' :
1929
+ saveStatus === 'Error' ? 'bg-red-600 hover:bg-red-700' :
1930
+ 'bg-indigo-600 hover:bg-indigo-700'
1931
+ } text-white`}
1932
+ >
1933
+ {saveStatus}
1934
+ </button>
1935
+ <button
1936
+ onClick={handlePreviewTranslation}
1937
+ className={`px-4 py-2 rounded-lg flex items-center space-x-2 ${
1938
+ showTranslatedSubtitles
1939
+ ? 'bg-green-500 hover:bg-green-600 text-white'
1940
+ : 'bg-gray-500 hover:bg-gray-600 text-white'
1941
+ }`}
1942
+ >
1943
+ <span>{showTranslatedSubtitles ? 'Show Original' : 'Show Translation'}</span>
1944
+ </button>
1945
+ </div>
1946
+
1947
+ {/* Translation Progress */}
1948
+ <div className="mb-4">
1949
+ <div className="flex justify-between items-center mb-2">
1950
+ <span className="text-sm font-medium text-gray-700">Translation Progress</span>
1951
+ <span className="text-sm text-gray-600">{Object.keys(subtitleTranslations).length} of {subtitleSegments.length} segments completed</span>
1952
+ </div>
1953
+ <div className="w-full bg-gray-200 rounded-full h-2">
1954
+ <div
1955
+ className="bg-green-500 h-2 rounded-full transition-all duration-300"
1956
+ style={{ width: `${(Object.keys(subtitleTranslations).length / subtitleSegments.length) * 100}%` }}
1957
+ ></div>
1958
+ </div>
1959
+ </div>
1960
+ </div>
1961
+
1962
+ {/* Submissions Panel - Only show for Week 2 subtitling */}
1963
+ - {selectedWeek === 2 && (
1964
+ + {false && selectedWeek === 2 && (
1965
+ <div className="mt-6 col-span-2">
1966
+ <div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
1967
+ <div className="flex items-center justify-between mb-4">
1968
+ <h3 className="text-lg font-semibold text-gray-900">
1969
+ 👥 View Submissions for Segment {currentSegment}
1970
+ </h3>
1971
+ <button
1972
+ onClick={toggleSubmissions}
1973
+ className={`px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors duration-200 ${
1974
+ showSubmissions
1975
+ ? 'bg-gray-500 hover:bg-gray-600 text-white'
1976
+ : 'bg-purple-600 hover:bg-purple-700 text-white'
1977
+ }`}
1978
+ >
1979
+ <span>{showSubmissions ? 'Hide Submissions' : 'Show Submissions'}</span>
1980
+ <span>{showSubmissions ? '▼' : '▶'}</span>
1981
+ </button>
1982
+ </div>
1983
+
1984
+ {showSubmissions && (
1985
+ <div className="space-y-4">
1986
+ {loadingSubmissions ? (
1987
+ <div className="text-center py-8">
1988
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto mb-4"></div>
1989
+ <p className="text-gray-600">Loading submissions...</p>
1990
+ </div>
1991
+ ) : submissions.length > 0 ? (
1992
+ <>
1993
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
1994
+ {submissions.map((submission) => (
1995
+ <div key={submission._id} className="bg-gray-50 rounded-lg p-4 border border-gray-200">
1996
+ <div className="flex items-center justify-between mb-2">
1997
+ <span className="font-medium text-gray-900">
1998
+ {submission.username}
1999
+ </span>
2000
+ <div className="flex items-center space-x-2">
2001
+ <span className="text-xs text-gray-500">
2002
+ {new Date(submission.submissionDate).toLocaleString()}
2003
+ </span>
2004
+ {((localStorage.getItem('viewMode')||'auto') === 'student') ? false : (JSON.parse(localStorage.getItem('user') || '{}').role === 'admin') && (
2005
+ <button
2006
+ className="px-2 py-1 text-xs bg-red-500 text-white rounded hover:bg-red-600"
2007
+ onClick={async () => {
2008
+ try {
2009
+ await api.delete(`/api/subtitles/${submission._id}`);
2010
+ await fetchUserSubmissions(weeklyPractice);
2011
+ } catch (e) {
2012
+ console.error('Delete subtitle submission failed', e);
2013
+ }
2014
+ }}
2015
+ >
2016
+ Delete
2017
+ </button>
2018
+ )}
2019
+ </div>
2020
+ </div>
2021
+ <div className="text-sm text-gray-800 whitespace-pre-wrap">{submission.chineseTranslation}</div>
2022
+ </div>
2023
+ ))}
2024
+ </div>
2025
+ </>
2026
+ ) : (
2027
+ <div className="text-gray-600">No submissions yet.</div>
2028
+ )}
2029
+ </div>
2030
+ )}
2031
+ </div>
2032
+ </div>
2033
+ )}
2034
+ </div>
2035
  ) : (
2036
  /* Weekly Practice */
2037
  <div className="space-y-6">