Upload folder using huggingface_hub
Browse files
client/src/pages/TutorialTasks.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { useState, useEffect, useCallback } from 'react';
|
| 2 |
import { useNavigate } from 'react-router-dom';
|
| 3 |
import { api } from '../services/api';
|
| 4 |
import {
|
|
@@ -63,6 +63,14 @@ const TutorialTasks: React.FC = () => {
|
|
| 63 |
const [tutorialWeek, setTutorialWeek] = useState<TutorialWeek | null>(null);
|
| 64 |
const [userSubmissions, setUserSubmissions] = useState<{[key: string]: UserSubmission[]}>({});
|
| 65 |
const [sourceHeights, setSourceHeights] = useState<{[key: string]: number}>({});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
// Move a task up or down by normalizing positions for the current visible list (weeks 4–6 only)
|
| 68 |
const moveTask = async (taskId: string, direction: 'up' | 'down') => {
|
|
@@ -111,8 +119,7 @@ const TutorialTasks: React.FC = () => {
|
|
| 111 |
console.error('Reorder failed', error);
|
| 112 |
}
|
| 113 |
};
|
| 114 |
-
|
| 115 |
-
const [submitting, setSubmitting] = useState<{[key: string]: boolean}>({});
|
| 116 |
const [isWeekHidden, setIsWeekHidden] = useState<boolean>(false);
|
| 117 |
const [translationText, setTranslationText] = useState<{[key: string]: string}>({});
|
| 118 |
const [selectedGroups, setSelectedGroups] = useState<{[key: string]: number}>({});
|
|
@@ -1106,6 +1113,25 @@ const TutorialTasks: React.FC = () => {
|
|
| 1106 |
|
| 1107 |
// Remove intrusive loading screen - just show content with loading state
|
| 1108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1109 |
return (
|
| 1110 |
<div className="min-h-screen bg-white py-8">
|
| 1111 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
@@ -1640,7 +1666,12 @@ const TutorialTasks: React.FC = () => {
|
|
| 1640 |
</div>
|
| 1641 |
) : (
|
| 1642 |
tutorialTasks.map((task) => (
|
| 1643 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1644 |
<div className="mb-6">
|
| 1645 |
<div className="flex items-center justify-between mb-4">
|
| 1646 |
<div className="flex items-center space-x-3">
|
|
@@ -1852,7 +1883,9 @@ const TutorialTasks: React.FC = () => {
|
|
| 1852 |
<label className="block text-xs font-medium text-gray-700 mb-1">Select Your Group</label>
|
| 1853 |
<select
|
| 1854 |
value={selectedGroups[task._id] || ''}
|
| 1855 |
-
|
|
|
|
|
|
|
| 1856 |
className="w-40 px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-xs"
|
| 1857 |
>
|
| 1858 |
<option value="">Choose...</option>
|
|
@@ -1867,14 +1900,17 @@ const TutorialTasks: React.FC = () => {
|
|
| 1867 |
<textarea
|
| 1868 |
id={`tutorial-translation-${task._id}`}
|
| 1869 |
value={translationText[task._id] || ''}
|
|
|
|
|
|
|
| 1870 |
onChange={(e) => setTranslationText({ ...translationText, [task._id]: e.target.value })}
|
|
|
|
| 1871 |
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
| 1872 |
style={{ height: sourceHeights[task._id] ? `${sourceHeights[task._id]}px` : 'auto' }}
|
| 1873 |
rows={4}
|
| 1874 |
placeholder="Enter your group's translation here..."
|
| 1875 |
/>
|
| 1876 |
<div className="flex justify-end mt-2">
|
| 1877 |
-
<button onClick={() => handleSubmitTranslation(task._id)} disabled={submitting[task._id]} className="btn-primary disabled:bg-gray-400 text-white px-4 py-2 rounded-lg text-sm">{submitting[task._id] ? 'Submitting...' : 'Submit Translation'}</button>
|
| 1878 |
</div>
|
| 1879 |
</div>
|
| 1880 |
)}
|
|
@@ -2037,7 +2073,9 @@ const TutorialTasks: React.FC = () => {
|
|
| 2037 |
</label>
|
| 2038 |
<select
|
| 2039 |
value={selectedGroups[task._id] || ''}
|
| 2040 |
-
|
|
|
|
|
|
|
| 2041 |
className="w-48 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-sm"
|
| 2042 |
required
|
| 2043 |
>
|
|
@@ -2062,15 +2100,19 @@ const TutorialTasks: React.FC = () => {
|
|
| 2062 |
<textarea
|
| 2063 |
id={`tutorial-translation-${task._id}`}
|
| 2064 |
value={translationText[task._id] || ''}
|
|
|
|
|
|
|
| 2065 |
onChange={(e) => setTranslationText({ ...translationText, [task._id]: e.target.value })}
|
|
|
|
| 2066 |
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
|
|
|
| 2067 |
rows={4}
|
| 2068 |
placeholder="Enter your group's translation here..."
|
| 2069 |
/>
|
| 2070 |
</div>
|
| 2071 |
|
| 2072 |
<button
|
| 2073 |
-
onClick={() => handleSubmitTranslation(task._id)}
|
| 2074 |
disabled={submitting[task._id]}
|
| 2075 |
className="relative inline-flex items-center justify-center gap-2 px-4 py-2 rounded-2xl text-sm font-medium text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,0.6),inset_0_-1px_0_rgba(0,0,0,0.12)] bg-sky-600/70 disabled:bg-gray-400"
|
| 2076 |
>
|
|
|
|
| 1 |
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
| 2 |
import { useNavigate } from 'react-router-dom';
|
| 3 |
import { api } from '../services/api';
|
| 4 |
import {
|
|
|
|
| 63 |
const [tutorialWeek, setTutorialWeek] = useState<TutorialWeek | null>(null);
|
| 64 |
const [userSubmissions, setUserSubmissions] = useState<{[key: string]: UserSubmission[]}>({});
|
| 65 |
const [sourceHeights, setSourceHeights] = useState<{[key: string]: number}>({});
|
| 66 |
+
const [loading, setLoading] = useState(true);
|
| 67 |
+
const [submitting, setSubmitting] = useState<{[key: string]: boolean}>({});
|
| 68 |
+
const [isWeekHidden, setIsWeekHidden] = useState<boolean>(false);
|
| 69 |
+
const [translationText, setTranslationText] = useState<{[key: string]: string}>({});
|
| 70 |
+
const [selectedGroups, setSelectedGroups] = useState<{[key: string]: number}>({});
|
| 71 |
+
const [expandedSections, setExpandedSections] = useState<{[key: string]: boolean}>({});
|
| 72 |
+
const [lockedCardHeights, setLockedCardHeights] = useState<{[key: string]: number}>({});
|
| 73 |
+
const cardRefs = useRef<{[key: string]: HTMLDivElement | null}>({});
|
| 74 |
|
| 75 |
// Move a task up or down by normalizing positions for the current visible list (weeks 4–6 only)
|
| 76 |
const moveTask = async (taskId: string, direction: 'up' | 'down') => {
|
|
|
|
| 119 |
console.error('Reorder failed', error);
|
| 120 |
}
|
| 121 |
};
|
| 122 |
+
|
|
|
|
| 123 |
const [isWeekHidden, setIsWeekHidden] = useState<boolean>(false);
|
| 124 |
const [translationText, setTranslationText] = useState<{[key: string]: string}>({});
|
| 125 |
const [selectedGroups, setSelectedGroups] = useState<{[key: string]: number}>({});
|
|
|
|
| 1113 |
|
| 1114 |
// Remove intrusive loading screen - just show content with loading state
|
| 1115 |
|
| 1116 |
+
const lockCardHeight = (taskId: string) => {
|
| 1117 |
+
try {
|
| 1118 |
+
const el = cardRefs.current[taskId];
|
| 1119 |
+
if (!el) return;
|
| 1120 |
+
const h = el.getBoundingClientRect().height;
|
| 1121 |
+
if (h && h > 0) {
|
| 1122 |
+
setLockedCardHeights(prev => ({ ...prev, [taskId]: Math.ceil(h) }));
|
| 1123 |
+
}
|
| 1124 |
+
} catch {}
|
| 1125 |
+
};
|
| 1126 |
+
|
| 1127 |
+
const unlockCardHeight = (taskId: string) => {
|
| 1128 |
+
setLockedCardHeights(prev => {
|
| 1129 |
+
const next = { ...prev } as any;
|
| 1130 |
+
delete next[taskId];
|
| 1131 |
+
return next;
|
| 1132 |
+
});
|
| 1133 |
+
};
|
| 1134 |
+
|
| 1135 |
return (
|
| 1136 |
<div className="min-h-screen bg-white py-8">
|
| 1137 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
|
|
| 1666 |
</div>
|
| 1667 |
) : (
|
| 1668 |
tutorialTasks.map((task) => (
|
| 1669 |
+
<div
|
| 1670 |
+
key={task._id}
|
| 1671 |
+
ref={(el) => { (cardRefs.current[task._id] = el); }}
|
| 1672 |
+
className="bg-white rounded-xl shadow-lg border border-gray-100 p-8 hover:shadow-xl transition-shadow duration-300"
|
| 1673 |
+
style={lockedCardHeights[task._id] ? { height: lockedCardHeights[task._id], overflow: 'hidden' } : undefined}
|
| 1674 |
+
>
|
| 1675 |
<div className="mb-6">
|
| 1676 |
<div className="flex items-center justify-between mb-4">
|
| 1677 |
<div className="flex items-center space-x-3">
|
|
|
|
| 1883 |
<label className="block text-xs font-medium text-gray-700 mb-1">Select Your Group</label>
|
| 1884 |
<select
|
| 1885 |
value={selectedGroups[task._id] || ''}
|
| 1886 |
+
onFocus={() => lockCardHeight(task._id)}
|
| 1887 |
+
onChange={(e) => { lockCardHeight(task._id); setSelectedGroups({ ...selectedGroups, [task._id]: parseInt(e.target.value) }); }}
|
| 1888 |
+
onBlur={() => unlockCardHeight(task._id)}
|
| 1889 |
className="w-40 px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-xs"
|
| 1890 |
>
|
| 1891 |
<option value="">Choose...</option>
|
|
|
|
| 1900 |
<textarea
|
| 1901 |
id={`tutorial-translation-${task._id}`}
|
| 1902 |
value={translationText[task._id] || ''}
|
| 1903 |
+
onFocus={() => lockCardHeight(task._id)}
|
| 1904 |
+
onInput={() => lockCardHeight(task._id)}
|
| 1905 |
onChange={(e) => setTranslationText({ ...translationText, [task._id]: e.target.value })}
|
| 1906 |
+
onBlur={() => unlockCardHeight(task._id)}
|
| 1907 |
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
| 1908 |
style={{ height: sourceHeights[task._id] ? `${sourceHeights[task._id]}px` : 'auto' }}
|
| 1909 |
rows={4}
|
| 1910 |
placeholder="Enter your group's translation here..."
|
| 1911 |
/>
|
| 1912 |
<div className="flex justify-end mt-2">
|
| 1913 |
+
<button onClick={() => { lockCardHeight(task._id); handleSubmitTranslation(task._id).finally(() => setTimeout(() => unlockCardHeight(task._id), 300)); }} disabled={submitting[task._id]} className="btn-primary disabled:bg-gray-400 text-white px-4 py-2 rounded-lg text-sm">{submitting[task._id] ? 'Submitting...' : 'Submit Translation'}</button>
|
| 1914 |
</div>
|
| 1915 |
</div>
|
| 1916 |
)}
|
|
|
|
| 2073 |
</label>
|
| 2074 |
<select
|
| 2075 |
value={selectedGroups[task._id] || ''}
|
| 2076 |
+
onFocus={() => lockCardHeight(task._id)}
|
| 2077 |
+
onChange={(e) => { lockCardHeight(task._id); setSelectedGroups({ ...selectedGroups, [task._id]: parseInt(e.target.value) }); }}
|
| 2078 |
+
onBlur={() => unlockCardHeight(task._id)}
|
| 2079 |
className="w-48 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white text-sm"
|
| 2080 |
required
|
| 2081 |
>
|
|
|
|
| 2100 |
<textarea
|
| 2101 |
id={`tutorial-translation-${task._id}`}
|
| 2102 |
value={translationText[task._id] || ''}
|
| 2103 |
+
onFocus={() => lockCardHeight(task._id)}
|
| 2104 |
+
onInput={() => lockCardHeight(task._id)}
|
| 2105 |
onChange={(e) => setTranslationText({ ...translationText, [task._id]: e.target.value })}
|
| 2106 |
+
onBlur={() => unlockCardHeight(task._id)}
|
| 2107 |
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 bg-white"
|
| 2108 |
+
style={{ height: sourceHeights[task._id] ? `${sourceHeights[task._id]}px` : 'auto' }}
|
| 2109 |
rows={4}
|
| 2110 |
placeholder="Enter your group's translation here..."
|
| 2111 |
/>
|
| 2112 |
</div>
|
| 2113 |
|
| 2114 |
<button
|
| 2115 |
+
onClick={() => { lockCardHeight(task._id); handleSubmitTranslation(task._id).finally(() => setTimeout(() => unlockCardHeight(task._id), 300)); }}
|
| 2116 |
disabled={submitting[task._id]}
|
| 2117 |
className="relative inline-flex items-center justify-center gap-2 px-4 py-2 rounded-2xl text-sm font-medium text-white ring-1 ring-inset ring-white/50 backdrop-blur-md backdrop-brightness-110 backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,0.6),inset_0_-1px_0_rgba(0,0,0,0.12)] bg-sky-600/70 disabled:bg-gray-400"
|
| 2118 |
>
|