add more things

This commit is contained in:
rgrgogu
2026-07-20 22:06:18 +08:00
parent f23b022a6f
commit 77e758c38b
14 changed files with 350 additions and 38 deletions
@@ -20,6 +20,7 @@ import { buildSelectionActions } from "../../config/users/selection.config";
import { buildRowActions } from "../../config/users/rowActions.config";
import { USER_STAT_MAP } from "@/data/adminDashboard.data";
import { ROLE_CONFIG } from "@/data/profile.data";
import { getTimestamp } from "@/utils/timestamp.util";
export default function UsersTable() {
@@ -58,11 +59,17 @@ export default function UsersTable() {
tableRefsRef.current = refs; // ← just store directly, no override needed
};
const generatedBy = currentUser
? `${currentUser.personal_info?.name?.full_name ?? currentUser.email} (${ROLE_CONFIG[currentUser.acc_type]?.label ?? currentUser.acc_type})`
: undefined;
const exportConfig = {
allData: users,
attributes,
filename: `${getTimestamp()}_Users`,
sheetName: "Users",
title: "Users",
generatedBy,
};
const rowActions = buildRowActions({
@@ -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>
);
}