mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
implemented task more
This commit is contained in:
@@ -14,7 +14,7 @@ import { Switch } from "@/components/ui/switch";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
import { CRON_PRESET_OPTIONS, JOB_LABELS } from "@/data/cronPresets.data";
|
||||
import { CRON_PRESET_OPTIONS, JOB_LABELS, TARGET_STATUS_OPTIONS } from "@/data/cronPresets.data";
|
||||
|
||||
function SectionCard({ children }) {
|
||||
return <div className="rounded-lg border bg-card p-4">{children}</div>;
|
||||
@@ -84,6 +84,23 @@ export default function Jobs() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTargetStatusChange(jobName, target_status) {
|
||||
setSavingJob(jobName);
|
||||
try {
|
||||
const { data } = await api.patch(`/admin/announcement-settings/${jobName}`, {
|
||||
target_status,
|
||||
updatedBy: user?.user_id ?? null,
|
||||
});
|
||||
const updated = data?.data?.data;
|
||||
setSettings((prev) => prev.map((s) => (s.job_name === jobName ? { ...s, ...updated, target_status } : s)));
|
||||
toast("Target status updated.");
|
||||
} catch (err) {
|
||||
toast(err?.response?.data?.message ?? "Failed to update target status.");
|
||||
} finally {
|
||||
setSavingJob(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="bg-muted h-full">
|
||||
<div className="lg:container lg:mx-auto lg:px-0 flex flex-col items-center px-4">
|
||||
@@ -151,6 +168,27 @@ export default function Jobs() {
|
||||
</Select>
|
||||
{isSaving && <Spinner className="size-3.5" />}
|
||||
</div>
|
||||
|
||||
{s.target_status != null && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">Marks tasks as:</span>
|
||||
<Select
|
||||
value={s.target_status}
|
||||
disabled={isSaving}
|
||||
onValueChange={(v) => handleTargetStatusChange(s.job_name, v)}
|
||||
>
|
||||
<SelectTrigger className="w-[180px] h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TARGET_STATUS_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{isSaving && <Spinner className="size-3.5" />}
|
||||
</div>
|
||||
)}
|
||||
</SectionCard>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function ViewTaskList() {
|
||||
const navigate = useNavigate();
|
||||
const { taskListId } = useParams();
|
||||
const { fetchTaskList } = useAdminTask();
|
||||
const { fmtDate } = useDateFormat();
|
||||
const { fmtDateTime } = useDateFormat();
|
||||
|
||||
const [taskList, setTaskList] = useState(null);
|
||||
|
||||
@@ -275,7 +275,7 @@ export default function ViewTaskList() {
|
||||
<span className="text-sm">
|
||||
Deadline:{' '}
|
||||
<span className="text-foreground font-medium">
|
||||
{fmtDate(task.deadline)}
|
||||
{fmtDateTime(task.deadline)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -15,13 +15,15 @@ import { RestoreDialog } from '@/components/generic/Dialogs/RestoreDialog';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
||||
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { House, NotebookPen, Users, Paperclip, Check, X } from 'lucide-react';
|
||||
import AppBreadcrumb from '@/components/generic/Breadcrumb/AppBreadcrumb';
|
||||
import { formatDate } from '@/utils/table.util';
|
||||
import { formatDate, formatDateTime } from '@/utils/table.util';
|
||||
import { getTimestamp } from '@/utils/timestamp.util';
|
||||
|
||||
import { buildDataColumns, columnPinning } from '@/modules/admin/config/task_list/task_completion/columns.config';
|
||||
@@ -51,13 +53,14 @@ export default function TaskCompletions() {
|
||||
task, taskList,
|
||||
completions, completionPagination, setCompletionPagination, completionLoading,
|
||||
completionAttributes,
|
||||
fetchTask, fetchTaskList,
|
||||
fetchTask, fetchTaskList, updateTask,
|
||||
fetchCompletions, reviewSubmission,
|
||||
archiveCompletion, restoreCompletion,
|
||||
bulkArchiveCompletions, bulkRestoreCompletions,
|
||||
} = useAdminTask();
|
||||
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
const [togglingSubmissions, setTogglingSubmissions] = useState(false);
|
||||
const [archiveTarget, setArchiveTarget] = useState(null);
|
||||
const [restoreTarget, setRestoreTarget] = useState(null);
|
||||
const [bulkArchiveIds, setBulkArchiveIds] = useState(null);
|
||||
@@ -78,6 +81,17 @@ export default function TaskCompletions() {
|
||||
|
||||
const handleRefsReady = (refs) => { tableRefsRef.current = refs; };
|
||||
|
||||
// ── Toggle accepting submissions ─────────────────────────────────────────
|
||||
async function handleToggleAcceptingSubmissions(v) {
|
||||
setTogglingSubmissions(true);
|
||||
try {
|
||||
await updateTask(taskListId, taskId, { accepts_submissions: v });
|
||||
await fetchTask(taskListId, taskId);
|
||||
} finally {
|
||||
setTogglingSubmissions(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Fetch handler ─────────────────────────────────────────────────────────
|
||||
const handleFetch = useCallback((params) => {
|
||||
return fetchCompletions(taskListId, taskId, params);
|
||||
@@ -186,18 +200,28 @@ export default function TaskCompletions() {
|
||||
|
||||
{/* ── Detail card ───────────────────────────────────────────────── */}
|
||||
<div className="bg-card border rounded-xl p-5 flex flex-col gap-4 w-full mb-6">
|
||||
<div className="flex flex-col gap-1 min-w-0">
|
||||
{task
|
||||
? <h1 className="text-lg font-medium leading-none">{task.name}</h1>
|
||||
: <Skeleton className="h-5 w-48" />
|
||||
}
|
||||
{task
|
||||
? <p className="text-sm text-muted-foreground mt-1">
|
||||
{taskList?.name ?? '—'}
|
||||
{task.deadline ? ` · Due ${formatDate(task.deadline)}` : ''}
|
||||
</p>
|
||||
: <Skeleton className="h-4 w-72 mt-1" />
|
||||
}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex flex-col gap-1 min-w-0">
|
||||
{task
|
||||
? <h1 className="text-lg font-medium leading-none">{task.name}</h1>
|
||||
: <Skeleton className="h-5 w-48" />
|
||||
}
|
||||
{task
|
||||
? <p className="text-sm text-muted-foreground mt-1">
|
||||
{taskList?.name ?? '—'}
|
||||
{task.deadline ? ` · Due ${formatDateTime(task.deadline)}` : ''}
|
||||
</p>
|
||||
: <Skeleton className="h-4 w-72 mt-1" />
|
||||
}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<Label className="text-sm text-muted-foreground">Accepting submissions</Label>
|
||||
<Switch
|
||||
checked={task?.accepts_submissions !== false}
|
||||
disabled={!task || togglingSubmissions}
|
||||
onCheckedChange={handleToggleAcceptingSubmissions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
FileVideo, FileAudio, File, ArrowLeft,
|
||||
Clock, CalendarDays, ExternalLink, NotebookPen,
|
||||
} from 'lucide-react';
|
||||
import { formatDate } from '@/utils/table.util';
|
||||
import { formatDate, formatDateTime } from '@/utils/table.util';
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
const getInitials = (name = '') =>
|
||||
@@ -275,7 +275,7 @@ export default function ViewTaskCompletion() {
|
||||
{task?.deadline && (
|
||||
<span className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<CalendarDays className="size-3.5" />
|
||||
Due {formatDate(task.deadline)}
|
||||
Due {formatDateTime(task.deadline)}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1.5 text-muted-foreground">
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
Search, RefreshCw, ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight,
|
||||
ArrowRight, Check, AlertTriangle, Circle,
|
||||
} from 'lucide-react';
|
||||
import { formatDate } from '@/utils/table.util';
|
||||
import { formatDateTime } from '@/utils/table.util';
|
||||
|
||||
const PAGE_SIZES = [50, 100, 1000];
|
||||
|
||||
@@ -168,7 +168,7 @@ export default function ClientTaskListTable({
|
||||
{tl.description || '—'}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm whitespace-nowrap">
|
||||
{task?.deadline ? formatDate(task.deadline) : '—'}
|
||||
{task?.deadline ? formatDateTime(task.deadline) : '—'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge status={statusLabel} />
|
||||
|
||||
@@ -35,7 +35,7 @@ import { useTask } from '@/contexts/ClientTaskContext';
|
||||
import { PageMeta } from '@/contexts/MetadataContext';
|
||||
import { useTaskProgress } from '@/contexts/ClientTaskProgressContext';
|
||||
import { useGroup } from '@/contexts/ClientGroupContext';
|
||||
import { formatDate } from '@/utils/table.util';
|
||||
import { formatDate, formatDateTime } from '@/utils/table.util';
|
||||
import api from '@/utils/api.util';
|
||||
|
||||
// ─── Status badge ─────────────────────────────────────────────────────────────
|
||||
@@ -215,7 +215,7 @@ const FileRow = ({ file, onClick }) => {
|
||||
};
|
||||
|
||||
// ─── Submission panel (Your Work) — upload_file and/or submit_text ────────────
|
||||
const SubmissionPanel = ({ latestCompletion, onAddAttachment, submitting, onFileClick, requiresReview, hasTextRequirement }) => {
|
||||
const SubmissionPanel = ({ latestCompletion, onAddAttachment, submitting, onFileClick, requiresReview, hasTextRequirement, acceptsSubmissions }) => {
|
||||
const files = latestCompletion?.files ?? [];
|
||||
|
||||
return (
|
||||
@@ -264,9 +264,15 @@ const SubmissionPanel = ({ latestCompletion, onAddAttachment, submitting, onFile
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<Button className="w-full" onClick={onAddAttachment} disabled={submitting}>
|
||||
<Plus /> {latestCompletion ? 'Resubmit' : 'Turn In'}
|
||||
</Button>
|
||||
{acceptsSubmissions ? (
|
||||
<Button className="w-full" onClick={onAddAttachment} disabled={submitting}>
|
||||
<Plus /> {latestCompletion ? 'Resubmit' : 'Turn In'}
|
||||
</Button>
|
||||
) : (
|
||||
<p className="text-sm text-center text-muted-foreground py-2">
|
||||
This task no longer accepts submissions
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -498,7 +504,7 @@ const ViewTask = () => {
|
||||
<div className="flex items-center gap-4 text-sm [&_svg]:size-4">
|
||||
{task?.deadline && (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Clock /> Due {formatDate(task.deadline)}
|
||||
<Clock /> Due {formatDateTime(task.deadline)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -623,6 +629,7 @@ const ViewTask = () => {
|
||||
onFileClick={(file) => setPreviewFile(file)}
|
||||
requiresReview={requiresReview}
|
||||
hasTextRequirement={hasTextReq}
|
||||
acceptsSubmissions={task?.accepts_submissions !== false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -638,6 +645,7 @@ const ViewTask = () => {
|
||||
onFileClick={(file) => setPreviewFile(file)}
|
||||
requiresReview={requiresReview}
|
||||
hasTextRequirement={hasTextReq}
|
||||
acceptsSubmissions={task?.accepts_submissions !== false}
|
||||
/>
|
||||
)}
|
||||
<RequirementsStatusPanel
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
LaptopMinimal, Table, Lock,
|
||||
} from 'lucide-react';
|
||||
import { Tabs, TabsList, TabsPanel, TabsTab } from '@/components/coss/tabs';
|
||||
import { formatDate } from '@/utils/table.util';
|
||||
import { formatDateTime } from '@/utils/table.util';
|
||||
import { cn } from '@/lib/utils';
|
||||
import ClientTaskListTable from '../components/TaskListTable';
|
||||
|
||||
@@ -109,7 +109,7 @@ const TaskCard = ({ task, onClick, locked, lockedBy, onLockedClick }) => {
|
||||
</p>
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground mt-auto pt-1 [&_svg]:size-3.5">
|
||||
<Calendar />
|
||||
{task.deadline ? `Due ${formatDate(task.deadline)}` : 'No due date'}
|
||||
{task.deadline ? `Due ${formatDateTime(task.deadline)}` : 'No due date'}
|
||||
</div>
|
||||
{locked && (
|
||||
<p className="text-xs text-muted-foreground">Tap to see what's required to unlock this task.</p>
|
||||
|
||||
Reference in New Issue
Block a user