Upload folder using huggingface_hub
Browse files- client/src/pages/TutorialTasks.tsx +146 -145
client/src/pages/TutorialTasks.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import React, { useState, useEffect, useCallback, useRef, useTransition, useDeferredValue } from 'react';
|
| 2 |
import { useNavigate } from 'react-router-dom';
|
| 3 |
import { api } from '../services/api';
|
|
|
|
| 4 |
import {
|
| 5 |
AcademicCapIcon,
|
| 6 |
DocumentTextIcon,
|
|
@@ -39,7 +40,7 @@ interface UserSubmission {
|
|
| 39 |
}
|
| 40 |
|
| 41 |
interface WeekBrief {
|
| 42 |
-
|
| 43 |
weekNumber: number;
|
| 44 |
title: string;
|
| 45 |
description: string;
|
|
@@ -163,7 +164,7 @@ const TutorialTasks: React.FC = () => {
|
|
| 163 |
if (response.status >= 200 && response.status < 300) {
|
| 164 |
const result = response.data;
|
| 165 |
console.log('Submission created successfully:', result);
|
| 166 |
-
|
| 167 |
setTranslationText({ ...translationText, [taskId]: '' });
|
| 168 |
setSelectedGroups({ ...selectedGroups, [taskId]: 0 });
|
| 169 |
setSubmitting({ ...submitting, [taskId]: false });
|
|
@@ -229,7 +230,7 @@ const TutorialTasks: React.FC = () => {
|
|
| 229 |
const saveTask = async () => {
|
| 230 |
if (!editingTask || !editTaskText.trim()) return;
|
| 231 |
|
| 232 |
-
|
| 233 |
try {
|
| 234 |
const response = await api.put(`/api/tasks/${editingTask}`, {
|
| 235 |
content: editTaskText
|
|
@@ -254,8 +255,8 @@ const TutorialTasks: React.FC = () => {
|
|
| 254 |
const response = await api.put(`/api/tasks/${taskId}/move`, { direction });
|
| 255 |
if (response.status === 200) {
|
| 256 |
fetchTutorialTasks();
|
| 257 |
-
|
| 258 |
-
|
| 259 |
console.error('Error moving task:', error);
|
| 260 |
}
|
| 261 |
};
|
|
@@ -307,18 +308,18 @@ const TutorialTasks: React.FC = () => {
|
|
| 307 |
{/* Week Selector */}
|
| 308 |
<div className="flex items-center justify-center space-x-4 mb-8">
|
| 309 |
{[1, 2, 3, 4, 5].map((week) => (
|
| 310 |
-
|
| 311 |
-
|
| 312 |
onClick={() => setSelectedWeek(week)}
|
| 313 |
className={`px-6 py-3 rounded-lg font-medium transition-colors ${
|
| 314 |
-
|
| 315 |
-
|
| 316 |
: 'bg-white text-gray-700 hover:bg-gray-50 border border-gray-200'
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
</div>
|
| 323 |
|
| 324 |
{/* Admin Controls */}
|
|
@@ -346,11 +347,11 @@ const TutorialTasks: React.FC = () => {
|
|
| 346 |
isWeekHidden ? 'translate-x-5' : 'translate-x-0.5'
|
| 347 |
} mt-0.5`}
|
| 348 |
/>
|
| 349 |
-
|
| 350 |
-
|
| 351 |
</label>
|
| 352 |
-
</div>
|
| 353 |
</div>
|
|
|
|
| 354 |
</div>
|
| 355 |
)}
|
| 356 |
|
|
@@ -362,7 +363,7 @@ const TutorialTasks: React.FC = () => {
|
|
| 362 |
<DocumentTextIcon className="h-5 w-5 text-indigo-600" />
|
| 363 |
</div>
|
| 364 |
<h2 className="text-xl font-semibold text-gray-900">Translation Brief</h2>
|
| 365 |
-
|
| 366 |
<div className="prose prose-indigo max-w-none">
|
| 367 |
<p className="text-gray-700 leading-relaxed">
|
| 368 |
This week focuses on translating {selectedWeek === 1 ? 'basic conversational phrases' :
|
|
@@ -378,19 +379,19 @@ const TutorialTasks: React.FC = () => {
|
|
| 378 |
{/* Group Google Doc Section */}
|
| 379 |
{!isWeekHidden && (
|
| 380 |
<div ref={groupDocRef} className="bg-white rounded-lg p-6 mb-8 border border-gray-200 shadow-sm">
|
| 381 |
-
|
| 382 |
-
|
| 383 |
<div className="bg-green-100 rounded-full p-2">
|
| 384 |
<DocumentTextIcon className="h-5 w-5 text-green-600" />
|
| 385 |
</div>
|
| 386 |
<h3 className="text-lg font-semibold text-gray-900">Group Google Doc</h3>
|
| 387 |
</div>
|
| 388 |
-
|
| 389 |
|
| 390 |
-
|
| 391 |
<div className="flex items-center space-x-4">
|
| 392 |
-
|
| 393 |
-
|
| 394 |
placeholder="Enter Google Doc URL"
|
| 395 |
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500"
|
| 396 |
/>
|
|
@@ -402,38 +403,38 @@ const TutorialTasks: React.FC = () => {
|
|
| 402 |
</select>
|
| 403 |
<button className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
|
| 404 |
Add Doc Link
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
<div className="bg-gray-50 rounded-lg p-4">
|
| 409 |
<h4 className="font-medium text-gray-900 mb-2">All Groups</h4>
|
| 410 |
-
|
| 411 |
<div className="flex items-center justify-between py-2 px-3 bg-white rounded border">
|
| 412 |
<span className="text-sm text-gray-600">No Google Doc links added yet</span>
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
)}
|
| 419 |
|
| 420 |
{/* Tasks List */}
|
| 421 |
{!isWeekHidden && (
|
| 422 |
<div ref={listRef} className="space-y-8">
|
| 423 |
{tutorialTasks.length === 0 ? (
|
| 424 |
-
|
| 425 |
<div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4">
|
| 426 |
<DocumentTextIcon className="h-8 w-8 text-gray-400" />
|
| 427 |
</div>
|
| 428 |
<h3 className="text-lg font-medium text-gray-900 mb-2">No tasks available</h3>
|
| 429 |
<p className="text-gray-600">Tasks for this week will be added soon.</p>
|
| 430 |
-
|
| 431 |
-
|
| 432 |
tutorialTasks.map((task, index) => (
|
| 433 |
<div key={task._id} className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden" style={{ minHeight: '700px' }}>
|
| 434 |
<div className="p-6">
|
| 435 |
-
|
| 436 |
-
|
| 437 |
<div className="relative p-2 rounded-2xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)]">
|
| 438 |
<div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
|
| 439 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
|
|
@@ -441,63 +442,63 @@ const TutorialTasks: React.FC = () => {
|
|
| 441 |
<div className="pointer-events-none absolute inset-0 rounded-2xl" style={{ background: 'radial-gradient(120%_120%_at_50%_55%,rgba(0,0,0,0.04),rgba(0,0,0,0)_60%)', opacity: 0.9 }} />
|
| 442 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-sky-500/30 mix-blend-overlay opacity-35" />
|
| 443 |
<DocumentTextIcon className="relative h-5 w-5 text-ui-text" />
|
| 444 |
-
|
| 445 |
-
|
| 446 |
<h3 className="text-lg font-semibold text-gray-900 flex items-center">Source Text #{index + 1}
|
| 447 |
{isAdmin && selectedWeek >= 4 && (
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
|
|
|
| 462 |
</div>
|
| 463 |
-
</div>
|
| 464 |
{isAdmin && (
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
onClick={cancelEditTask}
|
| 481 |
className="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors duration-200"
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
<button
|
| 488 |
onClick={() => setEditingTask(task._id)}
|
| 489 |
className="bg-blue-100 hover:bg-blue-200 text-blue-700 px-3 py-1 rounded-lg transition-colors duration-200"
|
| 490 |
>
|
| 491 |
<PencilIcon className="h-4 w-4" />
|
| 492 |
</button>
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
|
| 498 |
{/* Source Text Content */}
|
| 499 |
<div className="bg-gray-50 rounded-lg p-4 mb-6">
|
| 500 |
-
|
| 501 |
<textarea
|
| 502 |
value={editTaskText}
|
| 503 |
onChange={(e) => setEditTaskText(e.target.value)}
|
|
@@ -506,62 +507,62 @@ const TutorialTasks: React.FC = () => {
|
|
| 506 |
) : (
|
| 507 |
<p className="text-gray-800 leading-relaxed whitespace-pre-wrap">{task.content}</p>
|
| 508 |
)}
|
| 509 |
-
|
| 510 |
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
<span>{expandedSections[task._id] ? 'Hide' : 'Show'}</span>
|
| 526 |
<ChevronDownIcon className={`h-4 w-4 transition-transform ${expandedSections[task._id] ? 'rotate-180' : ''}`} />
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
{userSubmissions[task._id].map((submission, submissionIndex) => (
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
You
|
| 541 |
-
|
| 542 |
-
|
| 543 |
<span className="text-xs text-gray-500">Group {submission.groupNumber}</span>
|
| 544 |
-
|
| 545 |
-
|
| 546 |
{getStatusIcon(submission.status)}
|
| 547 |
{submission.isOwner && (
|
| 548 |
<div className="flex space-x-1">
|
| 549 |
-
|
| 550 |
-
|
| 551 |
className="text-blue-600 hover:text-blue-800 text-xs"
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
className="text-red-600 hover:text-red-800 text-xs"
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
</div>
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
<div className="text-sm text-gray-700 mb-2 flex-grow">
|
| 566 |
{editingSubmission?.id === submission._id ? (
|
| 567 |
<div className="space-y-2">
|
|
@@ -583,15 +584,15 @@ const TutorialTasks: React.FC = () => {
|
|
| 583 |
>
|
| 584 |
Cancel
|
| 585 |
</button>
|
| 586 |
-
|
| 587 |
-
|
| 588 |
) : (
|
| 589 |
<p className="whitespace-pre-wrap">{submission.transcreation}</p>
|
| 590 |
)}
|
| 591 |
-
|
| 592 |
-
|
| 593 |
))}
|
| 594 |
-
|
| 595 |
</div>
|
| 596 |
)}
|
| 597 |
|
|
@@ -606,15 +607,15 @@ const TutorialTasks: React.FC = () => {
|
|
| 606 |
onTranslationChange={(v) => setTranslationText(prev => ({ ...prev, [task._id]: v }))}
|
| 607 |
onSubmit={(localText, localGroup) => handleSubmitTranslation(task._id, localText, localGroup)}
|
| 608 |
/>
|
| 609 |
-
|
| 610 |
|
| 611 |
-
|
| 612 |
-
|
| 613 |
<div className="relative rounded-xl p-6 border border-gray-200">
|
| 614 |
<div className="absolute inset-0 rounded-xl bg-gradient-to-r from-gray-100/70 to-indigo-100/60" />
|
| 615 |
<div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-6">
|
| 616 |
<div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
|
| 617 |
-
|
| 618 |
<div className="relative p-2 rounded-2xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)]">
|
| 619 |
<div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
|
| 620 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
|
|
@@ -622,26 +623,26 @@ const TutorialTasks: React.FC = () => {
|
|
| 622 |
<div className="pointer-events-none absolute inset-0 rounded-2xl" style={{ background: 'radial-gradient(120%_120%_at_50%_55%,rgba(0,0,0,0.04),rgba(0,0,0,0)_60%)', opacity: 0.9 }} />
|
| 623 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-sky-500/30 mix-blend-overlay opacity-35" />
|
| 624 |
<DocumentTextIcon className="relative h-5 w-5 text-ui-text" />
|
| 625 |
-
|
| 626 |
<h4 className="text-gray-900 font-semibold text-lg">Group Translation</h4>
|
| 627 |
-
|
| 628 |
<div className="text-center py-8">
|
| 629 |
<p className="text-gray-600 mb-4">Please log in to submit your translation</p>
|
| 630 |
-
|
| 631 |
onClick={() => navigate('/login')}
|
| 632 |
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
| 633 |
-
|
| 634 |
Log In
|
| 635 |
-
|
| 636 |
</div>
|
| 637 |
</div>
|
|
|
|
|
|
|
| 638 |
</div>
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
)}
|
| 644 |
-
</div>
|
| 645 |
)}
|
| 646 |
</div>
|
| 647 |
|
|
@@ -833,4 +834,4 @@ const TranslationSection: React.FC<{
|
|
| 833 |
a.taskId === b.taskId
|
| 834 |
));
|
| 835 |
|
| 836 |
-
export default TutorialTasks;
|
|
|
|
| 1 |
import React, { useState, useEffect, useCallback, useRef, useTransition, useDeferredValue } from 'react';
|
| 2 |
import { useNavigate } from 'react-router-dom';
|
| 3 |
import { api } from '../services/api';
|
| 4 |
+
import { flushSync } from 'react-dom';
|
| 5 |
import {
|
| 6 |
AcademicCapIcon,
|
| 7 |
DocumentTextIcon,
|
|
|
|
| 40 |
}
|
| 41 |
|
| 42 |
interface WeekBrief {
|
| 43 |
+
_id: string;
|
| 44 |
weekNumber: number;
|
| 45 |
title: string;
|
| 46 |
description: string;
|
|
|
|
| 164 |
if (response.status >= 200 && response.status < 300) {
|
| 165 |
const result = response.data;
|
| 166 |
console.log('Submission created successfully:', result);
|
| 167 |
+
|
| 168 |
setTranslationText({ ...translationText, [taskId]: '' });
|
| 169 |
setSelectedGroups({ ...selectedGroups, [taskId]: 0 });
|
| 170 |
setSubmitting({ ...submitting, [taskId]: false });
|
|
|
|
| 230 |
const saveTask = async () => {
|
| 231 |
if (!editingTask || !editTaskText.trim()) return;
|
| 232 |
|
| 233 |
+
setSaving(true);
|
| 234 |
try {
|
| 235 |
const response = await api.put(`/api/tasks/${editingTask}`, {
|
| 236 |
content: editTaskText
|
|
|
|
| 255 |
const response = await api.put(`/api/tasks/${taskId}/move`, { direction });
|
| 256 |
if (response.status === 200) {
|
| 257 |
fetchTutorialTasks();
|
| 258 |
+
}
|
| 259 |
+
} catch (error) {
|
| 260 |
console.error('Error moving task:', error);
|
| 261 |
}
|
| 262 |
};
|
|
|
|
| 308 |
{/* Week Selector */}
|
| 309 |
<div className="flex items-center justify-center space-x-4 mb-8">
|
| 310 |
{[1, 2, 3, 4, 5].map((week) => (
|
| 311 |
+
<button
|
| 312 |
+
key={week}
|
| 313 |
onClick={() => setSelectedWeek(week)}
|
| 314 |
className={`px-6 py-3 rounded-lg font-medium transition-colors ${
|
| 315 |
+
selectedWeek === week
|
| 316 |
+
? 'bg-indigo-600 text-white'
|
| 317 |
: 'bg-white text-gray-700 hover:bg-gray-50 border border-gray-200'
|
| 318 |
+
}`}
|
| 319 |
+
>
|
| 320 |
+
Week {week}
|
| 321 |
+
</button>
|
| 322 |
+
))}
|
| 323 |
</div>
|
| 324 |
|
| 325 |
{/* Admin Controls */}
|
|
|
|
| 347 |
isWeekHidden ? 'translate-x-5' : 'translate-x-0.5'
|
| 348 |
} mt-0.5`}
|
| 349 |
/>
|
| 350 |
+
</div>
|
| 351 |
+
</div>
|
| 352 |
</label>
|
|
|
|
| 353 |
</div>
|
| 354 |
+
</div>
|
| 355 |
</div>
|
| 356 |
)}
|
| 357 |
|
|
|
|
| 363 |
<DocumentTextIcon className="h-5 w-5 text-indigo-600" />
|
| 364 |
</div>
|
| 365 |
<h2 className="text-xl font-semibold text-gray-900">Translation Brief</h2>
|
| 366 |
+
</div>
|
| 367 |
<div className="prose prose-indigo max-w-none">
|
| 368 |
<p className="text-gray-700 leading-relaxed">
|
| 369 |
This week focuses on translating {selectedWeek === 1 ? 'basic conversational phrases' :
|
|
|
|
| 379 |
{/* Group Google Doc Section */}
|
| 380 |
{!isWeekHidden && (
|
| 381 |
<div ref={groupDocRef} className="bg-white rounded-lg p-6 mb-8 border border-gray-200 shadow-sm">
|
| 382 |
+
<div className="flex items-center justify-between mb-4">
|
| 383 |
+
<div className="flex items-center space-x-3">
|
| 384 |
<div className="bg-green-100 rounded-full p-2">
|
| 385 |
<DocumentTextIcon className="h-5 w-5 text-green-600" />
|
| 386 |
</div>
|
| 387 |
<h3 className="text-lg font-semibold text-gray-900">Group Google Doc</h3>
|
| 388 |
</div>
|
| 389 |
+
</div>
|
| 390 |
|
| 391 |
+
<div className="space-y-4">
|
| 392 |
<div className="flex items-center space-x-4">
|
| 393 |
+
<input
|
| 394 |
+
type="text"
|
| 395 |
placeholder="Enter Google Doc URL"
|
| 396 |
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500"
|
| 397 |
/>
|
|
|
|
| 403 |
</select>
|
| 404 |
<button className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
|
| 405 |
Add Doc Link
|
| 406 |
+
</button>
|
| 407 |
+
</div>
|
| 408 |
+
|
| 409 |
<div className="bg-gray-50 rounded-lg p-4">
|
| 410 |
<h4 className="font-medium text-gray-900 mb-2">All Groups</h4>
|
| 411 |
+
<div className="space-y-2">
|
| 412 |
<div className="flex items-center justify-between py-2 px-3 bg-white rounded border">
|
| 413 |
<span className="text-sm text-gray-600">No Google Doc links added yet</span>
|
| 414 |
+
</div>
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
</div>
|
| 418 |
+
</div>
|
| 419 |
)}
|
| 420 |
|
| 421 |
{/* Tasks List */}
|
| 422 |
{!isWeekHidden && (
|
| 423 |
<div ref={listRef} className="space-y-8">
|
| 424 |
{tutorialTasks.length === 0 ? (
|
| 425 |
+
<div className="text-center py-12">
|
| 426 |
<div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4">
|
| 427 |
<DocumentTextIcon className="h-8 w-8 text-gray-400" />
|
| 428 |
</div>
|
| 429 |
<h3 className="text-lg font-medium text-gray-900 mb-2">No tasks available</h3>
|
| 430 |
<p className="text-gray-600">Tasks for this week will be added soon.</p>
|
| 431 |
+
</div>
|
| 432 |
+
) : (
|
| 433 |
tutorialTasks.map((task, index) => (
|
| 434 |
<div key={task._id} className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden" style={{ minHeight: '700px' }}>
|
| 435 |
<div className="p-6">
|
| 436 |
+
<div className="flex items-center justify-between mb-4">
|
| 437 |
+
<div className="flex items-center space-x-3">
|
| 438 |
<div className="relative p-2 rounded-2xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)]">
|
| 439 |
<div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
|
| 440 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
|
|
|
|
| 442 |
<div className="pointer-events-none absolute inset-0 rounded-2xl" style={{ background: 'radial-gradient(120%_120%_at_50%_55%,rgba(0,0,0,0.04),rgba(0,0,0,0)_60%)', opacity: 0.9 }} />
|
| 443 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-sky-500/30 mix-blend-overlay opacity-35" />
|
| 444 |
<DocumentTextIcon className="relative h-5 w-5 text-ui-text" />
|
| 445 |
+
</div>
|
| 446 |
+
<div>
|
| 447 |
<h3 className="text-lg font-semibold text-gray-900 flex items-center">Source Text #{index + 1}
|
| 448 |
{isAdmin && selectedWeek >= 4 && (
|
| 449 |
+
<span className="ml-2 inline-flex items-center space-x-1">
|
| 450 |
+
<button
|
| 451 |
+
className="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200"
|
| 452 |
+
onClick={() => moveTask(task._id, 'up')}
|
| 453 |
+
title="Move up"
|
| 454 |
+
>↑</button>
|
| 455 |
+
<button
|
| 456 |
+
className="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200"
|
| 457 |
+
onClick={() => moveTask(task._id, 'down')}
|
| 458 |
+
title="Move down"
|
| 459 |
+
>↓</button>
|
| 460 |
+
</span>
|
| 461 |
+
)}
|
| 462 |
+
</h3>
|
| 463 |
+
</div>
|
| 464 |
</div>
|
|
|
|
| 465 |
{isAdmin && (
|
| 466 |
+
<div className="flex items-center space-x-2">
|
| 467 |
+
{editingTask === task._id ? (
|
| 468 |
+
<>
|
| 469 |
+
<button
|
| 470 |
+
onClick={saveTask}
|
| 471 |
+
disabled={saving}
|
| 472 |
+
className="bg-green-100 hover:bg-green-200 text-green-700 px-3 py-1 rounded-lg transition-colors duration-200"
|
| 473 |
+
>
|
| 474 |
+
{saving ? (
|
| 475 |
+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-green-600"></div>
|
| 476 |
+
) : (
|
| 477 |
+
<CheckIcon className="h-4 w-4" />
|
| 478 |
+
)}
|
| 479 |
+
</button>
|
| 480 |
+
<button
|
| 481 |
onClick={cancelEditTask}
|
| 482 |
className="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors duration-200"
|
| 483 |
+
>
|
| 484 |
+
<XMarkIcon className="h-4 w-4" />
|
| 485 |
+
</button>
|
| 486 |
+
</>
|
| 487 |
+
) : (
|
| 488 |
<button
|
| 489 |
onClick={() => setEditingTask(task._id)}
|
| 490 |
className="bg-blue-100 hover:bg-blue-200 text-blue-700 px-3 py-1 rounded-lg transition-colors duration-200"
|
| 491 |
>
|
| 492 |
<PencilIcon className="h-4 w-4" />
|
| 493 |
</button>
|
| 494 |
+
)}
|
| 495 |
+
</div>
|
| 496 |
+
)}
|
| 497 |
+
</div>
|
| 498 |
|
| 499 |
{/* Source Text Content */}
|
| 500 |
<div className="bg-gray-50 rounded-lg p-4 mb-6">
|
| 501 |
+
{editingTask === task._id ? (
|
| 502 |
<textarea
|
| 503 |
value={editTaskText}
|
| 504 |
onChange={(e) => setEditTaskText(e.target.value)}
|
|
|
|
| 507 |
) : (
|
| 508 |
<p className="text-gray-800 leading-relaxed whitespace-pre-wrap">{task.content}</p>
|
| 509 |
)}
|
| 510 |
+
</div>
|
| 511 |
|
| 512 |
+
{/* All Submissions for this Task */}
|
| 513 |
+
{userSubmissions[task._id] && userSubmissions[task._id].length > 0 && (
|
| 514 |
+
<div className="bg-gradient-to-r from-white to-indigo-50 rounded-xl p-6 mb-6 border border-stone-200">
|
| 515 |
+
<div className="flex items-center justify-between mb-4">
|
| 516 |
+
<div className="flex items-center space-x-2">
|
| 517 |
+
<div className="bg-indigo-100 rounded-full p-1">
|
| 518 |
+
<CheckCircleIcon className="h-4 w-4 text-indigo-900" />
|
| 519 |
+
</div>
|
| 520 |
+
<h4 className="text-stone-900 font-semibold text-lg">All Submissions ({userSubmissions[task._id].length})</h4>
|
| 521 |
+
</div>
|
| 522 |
+
<button
|
| 523 |
+
onClick={() => toggleExpanded(task._id)}
|
| 524 |
+
className="flex items-center space-x-1 text-indigo-900 hover:text-indigo-900 text-sm font-medium"
|
| 525 |
+
>
|
| 526 |
<span>{expandedSections[task._id] ? 'Hide' : 'Show'}</span>
|
| 527 |
<ChevronDownIcon className={`h-4 w-4 transition-transform ${expandedSections[task._id] ? 'rotate-180' : ''}`} />
|
| 528 |
+
</button>
|
| 529 |
+
</div>
|
| 530 |
+
<div className={`grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 transition-all duration-300 ${
|
| 531 |
+
expandedSections[task._id]
|
| 532 |
+
? 'max-h-none overflow-visible'
|
| 533 |
+
: 'max-h-0 overflow-hidden'
|
| 534 |
+
}`}>
|
| 535 |
{userSubmissions[task._id].map((submission, submissionIndex) => (
|
| 536 |
+
<div key={submission._id} className="bg-white rounded-lg p-3 border border-stone-200 flex flex-col justify-between h-full">
|
| 537 |
+
<div className="flex items-center justify-between mb-2">
|
| 538 |
+
<div className="flex items-center space-x-2">
|
| 539 |
+
{submission.isOwner && (
|
| 540 |
+
<span className="inline-block bg-purple-100 text-purple-800 text-xs px-1.5 py-0.5 rounded-full">
|
| 541 |
You
|
| 542 |
+
</span>
|
| 543 |
+
)}
|
| 544 |
<span className="text-xs text-gray-500">Group {submission.groupNumber}</span>
|
| 545 |
+
</div>
|
| 546 |
+
<div className="flex items-center space-x-1">
|
| 547 |
{getStatusIcon(submission.status)}
|
| 548 |
{submission.isOwner && (
|
| 549 |
<div className="flex space-x-1">
|
| 550 |
+
<button
|
| 551 |
+
onClick={() => handleEditSubmission(submission._id, submission.transcreation)}
|
| 552 |
className="text-blue-600 hover:text-blue-800 text-xs"
|
| 553 |
+
>
|
| 554 |
+
Edit
|
| 555 |
+
</button>
|
| 556 |
+
<button
|
| 557 |
+
onClick={() => handleDeleteSubmission(submission._id)}
|
| 558 |
className="text-red-600 hover:text-red-800 text-xs"
|
| 559 |
+
>
|
| 560 |
+
Delete
|
| 561 |
+
</button>
|
| 562 |
</div>
|
| 563 |
+
)}
|
| 564 |
+
</div>
|
| 565 |
+
</div>
|
| 566 |
<div className="text-sm text-gray-700 mb-2 flex-grow">
|
| 567 |
{editingSubmission?.id === submission._id ? (
|
| 568 |
<div className="space-y-2">
|
|
|
|
| 584 |
>
|
| 585 |
Cancel
|
| 586 |
</button>
|
| 587 |
+
</div>
|
| 588 |
+
</div>
|
| 589 |
) : (
|
| 590 |
<p className="whitespace-pre-wrap">{submission.transcreation}</p>
|
| 591 |
)}
|
| 592 |
+
</div>
|
| 593 |
+
</div>
|
| 594 |
))}
|
| 595 |
+
</div>
|
| 596 |
</div>
|
| 597 |
)}
|
| 598 |
|
|
|
|
| 607 |
onTranslationChange={(v) => setTranslationText(prev => ({ ...prev, [task._id]: v }))}
|
| 608 |
onSubmit={(localText, localGroup) => handleSubmitTranslation(task._id, localText, localGroup)}
|
| 609 |
/>
|
| 610 |
+
)}
|
| 611 |
|
| 612 |
+
{/* Show login message for visitors */}
|
| 613 |
+
{!localStorage.getItem('token') && (
|
| 614 |
<div className="relative rounded-xl p-6 border border-gray-200">
|
| 615 |
<div className="absolute inset-0 rounded-xl bg-gradient-to-r from-gray-100/70 to-indigo-100/60" />
|
| 616 |
<div className="relative rounded-xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)] p-6">
|
| 617 |
<div className="pointer-events-none absolute inset-0 rounded-xl opacity-50 [background:linear-gradient(to_bottom,rgba(255,255,255,0.3),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.28),rgba(255,255,255,0)_28%)]" />
|
| 618 |
+
<div className="flex items-center space-x-2 mb-4">
|
| 619 |
<div className="relative p-2 rounded-2xl bg-white/10 backdrop-blur-md ring-1 ring-inset ring-white/30 shadow-[inset_0_0.5px_0_rgba(255,255,255,0.5),inset_0_-1px_1.5px_rgba(0,0,0,0.12)]">
|
| 620 |
<div className="pointer-events-none absolute inset-0 rounded-2xl opacity-60 [background:linear-gradient(to_bottom,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%),linear-gradient(to_right,rgba(255,255,255,0.35),rgba(255,255,255,0)_28%)]" />
|
| 621 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/30 via-white/10 to-transparent opacity-50" />
|
|
|
|
| 623 |
<div className="pointer-events-none absolute inset-0 rounded-2xl" style={{ background: 'radial-gradient(120%_120%_at_50%_55%,rgba(0,0,0,0.04),rgba(0,0,0,0)_60%)', opacity: 0.9 }} />
|
| 624 |
<div className="pointer-events-none absolute inset-0 rounded-2xl bg-sky-500/30 mix-blend-overlay opacity-35" />
|
| 625 |
<DocumentTextIcon className="relative h-5 w-5 text-ui-text" />
|
| 626 |
+
</div>
|
| 627 |
<h4 className="text-gray-900 font-semibold text-lg">Group Translation</h4>
|
| 628 |
+
</div>
|
| 629 |
<div className="text-center py-8">
|
| 630 |
<p className="text-gray-600 mb-4">Please log in to submit your translation</p>
|
| 631 |
+
<button
|
| 632 |
onClick={() => navigate('/login')}
|
| 633 |
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
| 634 |
+
>
|
| 635 |
Log In
|
| 636 |
+
</button>
|
| 637 |
</div>
|
| 638 |
</div>
|
| 639 |
+
</div>
|
| 640 |
+
)}
|
| 641 |
</div>
|
| 642 |
+
</div>
|
| 643 |
+
))
|
| 644 |
+
)}
|
| 645 |
+
</div>
|
|
|
|
|
|
|
| 646 |
)}
|
| 647 |
</div>
|
| 648 |
|
|
|
|
| 834 |
a.taskId === b.taskId
|
| 835 |
));
|
| 836 |
|
| 837 |
+
export default TutorialTasks;
|