mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
add more things
This commit is contained in:
@@ -250,6 +250,7 @@ export default function CreateTask() {
|
||||
units={units}
|
||||
lessons={lessons}
|
||||
quizzes={quizzes}
|
||||
taskListId={taskListId}
|
||||
/>
|
||||
{errors.requirements && (
|
||||
<p className="text-xs text-destructive pt-1">{errors.requirements}</p>
|
||||
|
||||
@@ -246,6 +246,7 @@ export default function EditTask() {
|
||||
units={units}
|
||||
lessons={lessons}
|
||||
quizzes={quizzes}
|
||||
taskListId={taskListId}
|
||||
/>
|
||||
{errors.requirements && (
|
||||
<p className="text-xs text-destructive pt-1">{errors.requirements}</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { Plus, Trash2, GripVertical, Link, Upload, BookOpen, Layers, FileText, ChevronsUpDown, Tag, Lock, Clock, AlertTriangle, PenLine, ClipboardCheck, ShieldCheck, Link2, Unlink } from 'lucide-react';
|
||||
import { Plus, Trash2, GripVertical, Link, Upload, BookOpen, Layers, FileText, ChevronsUpDown, Tag, Lock, Clock, AlertTriangle, PenLine, ClipboardCheck, ShieldCheck, Link2, Unlink, History } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -183,15 +183,16 @@ function createRequirement(type = 'visit_link') {
|
||||
}
|
||||
|
||||
// ─── RequirementBuilder ───────────────────────────────────────────────────────
|
||||
export default function RequirementBuilder({ value = [], onChange, courses = [], units = [], lessons = [], quizzes = [] }) {
|
||||
export default function RequirementBuilder({ value = [], onChange, courses = [], units = [], lessons = [], quizzes = [], taskListId }) {
|
||||
const [items, setItems] = useState(
|
||||
value.length > 0
|
||||
? value.map((r) => ({ _key: crypto.randomUUID(), ...r }))
|
||||
: []
|
||||
);
|
||||
const [tierCategories, setTierCategories] = useState([]);
|
||||
const [lockedDialog, setLockedDialog] = useState(null); // { title, tierLabel, contentType }
|
||||
const [noContentDialog, setNoContentDialog] = useState(null); // { title, contentType }
|
||||
const [lockedDialog, setLockedDialog] = useState(null); // { title, tierLabel, contentType }
|
||||
const [noContentDialog, setNoContentDialog] = useState(null); // { title, contentType }
|
||||
const [preCompletedDialog, setPreCompletedDialog] = useState(null); // { title, contentType, completedCount, totalAssignees }
|
||||
|
||||
useEffect(() => {
|
||||
api.get('/admin/tiers/categories')
|
||||
@@ -224,6 +225,8 @@ export default function RequirementBuilder({ value = [], onChange, courses = [],
|
||||
updateItem(key, { allowed_file_types: next });
|
||||
};
|
||||
|
||||
const CONTENT_TYPE_TO_REQUIREMENT_TYPE = { course: 'read_course', unit: 'read_unit', lesson: 'read_lesson', quiz: 'pass_quiz' };
|
||||
|
||||
const handleContentSelect = (key, content, contentType) => {
|
||||
updateItem(key, {
|
||||
reference_id: content.uuid,
|
||||
@@ -236,6 +239,30 @@ export default function RequirementBuilder({ value = [], onChange, courses = [],
|
||||
const { rank, label } = resolveTierBadge(content.subscription ?? 'free', tierMap);
|
||||
if (rank > 0) setLockedDialog({ title: content.title, tierLabel: label, contentType });
|
||||
}
|
||||
|
||||
// Heads-up only — informs the admin that some assignees already finished
|
||||
// this content before the requirement existed; task_progress auto-syncs
|
||||
// it as done for them (task_reading_progress_sync.service.js), it doesn't
|
||||
// need to be redone. Skipped entirely if there's no task list yet to check
|
||||
// assignees against (e.g. TaskQueueStep, mid Create Task List wizard).
|
||||
if (taskListId) {
|
||||
const type = CONTENT_TYPE_TO_REQUIREMENT_TYPE[contentType];
|
||||
api.get(`/admin/task-lists/${taskListId}/tasks/requirement-completion-check`, {
|
||||
params: { type, reference_id: content.uuid, reference_label: content.title },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const result = data?.data;
|
||||
if (result?.completedCount > 0) {
|
||||
setPreCompletedDialog({
|
||||
title: content.title,
|
||||
contentType,
|
||||
completedCount: result.completedCount,
|
||||
totalAssignees: result.totalAssignees,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => { });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -604,6 +631,29 @@ export default function RequirementBuilder({ value = [], onChange, courses = [],
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* ── Already-completed-by-assignees warning ─────────────────────── */}
|
||||
<AlertDialog open={!!preCompletedDialog} onOpenChange={(v) => !v && setPreCompletedDialog(null)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<History className="size-4 text-amber-500" />
|
||||
Already Completed by Some Assignees
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="space-y-1">
|
||||
<span className="block font-medium text-foreground">{preCompletedDialog?.title}</span>
|
||||
<span className="block">
|
||||
{preCompletedDialog?.completedCount} of {preCompletedDialog?.totalAssignees} assigned user(s) already
|
||||
completed this {preCompletedDialog?.contentType} before this requirement was added. It will
|
||||
automatically count as done for them — they won't need to redo it.
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogAction onClick={() => setPreCompletedDialog(null)}>Got it</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user