mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
task list working
This commit is contained in:
@@ -332,7 +332,7 @@ export function AdminTaskProvider({ children }) {
|
|||||||
request(async () => {
|
request(async () => {
|
||||||
const res = await api.post(`${BASE}/${taskListId}/tasks`, payload);
|
const res = await api.post(`${BASE}/${taskListId}/tasks`, payload);
|
||||||
toast('Task created.');
|
toast('Task created.');
|
||||||
return res.data?.data?.data ?? null;
|
return res.data?.data ?? null;
|
||||||
}),
|
}),
|
||||||
[request]
|
[request]
|
||||||
);
|
);
|
||||||
@@ -342,7 +342,7 @@ export function AdminTaskProvider({ children }) {
|
|||||||
request(async () => {
|
request(async () => {
|
||||||
const res = await api.patch(`${BASE}/${taskListId}/tasks/${taskId}`, payload);
|
const res = await api.patch(`${BASE}/${taskListId}/tasks/${taskId}`, payload);
|
||||||
toast('Task updated.');
|
toast('Task updated.');
|
||||||
return res.data?.data?.data ?? null;
|
return res.data?.data ?? null;
|
||||||
}),
|
}),
|
||||||
[request]
|
[request]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// config/columns.config.jsx
|
// config/columns.config.jsx
|
||||||
// Column definitions and pinning config for the Users table.
|
// Column definitions and pinning config for the Archived Tasks table.
|
||||||
|
|
||||||
|
import { format } from "date-fns";
|
||||||
import { buildColumns } from "@/utils/table.util";
|
import { buildColumns } from "@/utils/table.util";
|
||||||
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
||||||
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
||||||
@@ -10,8 +11,21 @@ export const columnPinning = {
|
|||||||
left: [],
|
left: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ─── Custom cell overrides ────────────────────────────────────────────────────
|
||||||
|
const cellOverrides = {
|
||||||
|
deadline: (info) => {
|
||||||
|
const value = info.getValue();
|
||||||
|
if (!value) return <span className="text-muted-foreground/40">-</span>;
|
||||||
|
return (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{format(new Date(value), "MMMM d, yyyy: h:mma")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the full column array for the Users table.
|
* Builds the full column array for the Archived Tasks table.
|
||||||
*
|
*
|
||||||
* @param {Array} attributes Field definitions from the server (drives data columns)
|
* @param {Array} attributes Field definitions from the server (drives data columns)
|
||||||
* @param {Array} rowActions Row-level kebab action definitions
|
* @param {Array} rowActions Row-level kebab action definitions
|
||||||
@@ -22,7 +36,7 @@ export function buildDataColumns(attributes, rowActions) {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
buildSelectionColumn(),
|
buildSelectionColumn(),
|
||||||
...buildColumns(visibleAttributes),
|
...buildColumns(visibleAttributes, { cellOverrides }),
|
||||||
buildRowActionsColumn(rowActions, { dropdownLabel: "User Actions" }),
|
buildRowActionsColumn(rowActions, { dropdownLabel: "Task Actions" }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// config/columns.config.jsx
|
// config/columns.config.jsx
|
||||||
// Column definitions and pinning config for the Users table.
|
// Column definitions and pinning config for the Tasks table.
|
||||||
|
|
||||||
|
import { format } from "date-fns";
|
||||||
import { buildColumns } from "@/utils/table.util";
|
import { buildColumns } from "@/utils/table.util";
|
||||||
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
||||||
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
||||||
@@ -10,8 +11,21 @@ export const columnPinning = {
|
|||||||
left: [],
|
left: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ─── Custom cell overrides ────────────────────────────────────────────────────
|
||||||
|
const cellOverrides = {
|
||||||
|
deadline: (info) => {
|
||||||
|
const value = info.getValue();
|
||||||
|
if (!value) return <span className="text-muted-foreground/40">-</span>;
|
||||||
|
return (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{format(new Date(value), "MMMM d, yyyy: h:mma")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the full column array for the Users table.
|
* Builds the full column array for the Tasks table.
|
||||||
*
|
*
|
||||||
* @param {Array} attributes Field definitions from the server (drives data columns)
|
* @param {Array} attributes Field definitions from the server (drives data columns)
|
||||||
* @param {Array} rowActions Row-level kebab action definitions
|
* @param {Array} rowActions Row-level kebab action definitions
|
||||||
@@ -22,7 +36,7 @@ export function buildDataColumns(attributes, rowActions) {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
buildSelectionColumn(),
|
buildSelectionColumn(),
|
||||||
...buildColumns(visibleAttributes),
|
...buildColumns(visibleAttributes, { cellOverrides }),
|
||||||
buildRowActionsColumn(rowActions, { dropdownLabel: "User Actions" }),
|
buildRowActionsColumn(rowActions, { dropdownLabel: "Task Actions" }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ export function buildRowActions({ navigate, onArchive, onRestore, onMoveUp, onMo
|
|||||||
icon: <ArrowUp className="size-4" />,
|
icon: <ArrowUp className="size-4" />,
|
||||||
onClick: (row) => onMoveUp(row),
|
onClick: (row) => onMoveUp(row),
|
||||||
hidden: () => showArchived,
|
hidden: () => showArchived,
|
||||||
|
separator: true,
|
||||||
disabled: (row) => tasks.findIndex((t) => t.task_id === row.task_id) <= 0,
|
disabled: (row) => tasks.findIndex((t) => t.task_id === row.task_id) <= 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -30,7 +31,6 @@ export function buildRowActions({ navigate, onArchive, onRestore, onMoveUp, onMo
|
|||||||
icon: <ArrowDown className="size-4" />,
|
icon: <ArrowDown className="size-4" />,
|
||||||
onClick: (row) => onMoveDown(row),
|
onClick: (row) => onMoveDown(row),
|
||||||
hidden: () => showArchived,
|
hidden: () => showArchived,
|
||||||
separator: true,
|
|
||||||
disabled: (row) => tasks.findIndex((t) => t.task_id === row.task_id) >= tasks.length - 1,
|
disabled: (row) => tasks.findIndex((t) => t.task_id === row.task_id) >= tasks.length - 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import DeadlinePicker from '@/components/generic/DeadlinePicker';
|
||||||
import {
|
import {
|
||||||
AlertDialog, AlertDialogAction, AlertDialogCancel,
|
AlertDialog, AlertDialogAction, AlertDialogCancel,
|
||||||
AlertDialogContent, AlertDialogDescription,
|
AlertDialogContent, AlertDialogDescription,
|
||||||
@@ -51,9 +52,7 @@ export default function EditTask() {
|
|||||||
setForm({
|
setForm({
|
||||||
name: data.name ?? '',
|
name: data.name ?? '',
|
||||||
description: data.description ?? '',
|
description: data.description ?? '',
|
||||||
deadline: data.deadline
|
deadline: data.deadline ?? '',
|
||||||
? new Date(data.deadline).toISOString().slice(0, 16)
|
|
||||||
: '',
|
|
||||||
status: data.status ?? 'pending',
|
status: data.status ?? 'pending',
|
||||||
is_required: data.is_required ?? true,
|
is_required: data.is_required ?? true,
|
||||||
requirements: reqs,
|
requirements: reqs,
|
||||||
@@ -185,32 +184,30 @@ export default function EditTask() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="space-y-3">
|
||||||
<div className="space-y-1">
|
<Label>Deadline</Label>
|
||||||
<Label htmlFor="deadline">Deadline</Label>
|
<DeadlinePicker
|
||||||
<Input
|
value={form.deadline}
|
||||||
id="deadline"
|
onChange={(iso) => setForm({ ...form, deadline: iso })}
|
||||||
type="datetime-local"
|
disabled={loading}
|
||||||
value={form.deadline}
|
/>
|
||||||
onChange={(e) => setForm({ ...form, deadline: e.target.value })}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
<div className="space-y-1">
|
||||||
<div className="space-y-1">
|
<Label>Status</Label>
|
||||||
<Label>Status</Label>
|
<Select
|
||||||
<Select
|
value={form.status}
|
||||||
value={form.status}
|
onValueChange={(v) => setForm({ ...form, status: v })}
|
||||||
onValueChange={(v) => setForm({ ...form, status: v })}
|
>
|
||||||
>
|
<SelectTrigger>
|
||||||
<SelectTrigger>
|
<SelectValue />
|
||||||
<SelectValue />
|
</SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectContent>
|
||||||
<SelectContent>
|
{STATUS_OPTIONS.map((s) => (
|
||||||
{STATUS_OPTIONS.map((s) => (
|
<SelectItem key={s.value} value={s.value}>{s.label}</SelectItem>
|
||||||
<SelectItem key={s.value} value={s.value}>{s.label}</SelectItem>
|
))}
|
||||||
))}
|
</SelectContent>
|
||||||
</SelectContent>
|
</Select>
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between rounded-md border px-3 py-2.5">
|
<div className="flex items-center justify-between rounded-md border px-3 py-2.5">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ArrowLeft, Pencil, FileText, CalendarClock,
|
ArrowLeft, Pencil, FileText, CalendarClock,
|
||||||
Link2, Upload, BookOpen, BookMarked, FileCheck2,
|
Link2, Upload, BookOpen, BookMarked, FileCheck2,
|
||||||
Info, GitPullRequest, Tag, Globe, Copy, File, Bookmark,
|
Info, GitPullRequest, Tag, Globe, Copy, File, Bookmark,
|
||||||
|
ClipboardCheck,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
// ─── Same config as ViewTaskList — label, icon only, all styling via shadcn tokens
|
// ─── Same config as ViewTaskList — label, icon only, all styling via shadcn tokens
|
||||||
@@ -128,8 +129,8 @@ function TaskRequirementsSection({ requirements = [] }) {
|
|||||||
export default function ViewTask() {
|
export default function ViewTask() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { taskListId, taskId } = useParams();
|
const { taskListId, taskId } = useParams();
|
||||||
const { fetchTask } = useAdminTask();
|
const { fetchTask, fetchCompletions, completionPagination, completionLoading } = useAdminTask();
|
||||||
const { fmtDate } = useDateFormat();
|
const { fmtDateTime } = useDateFormat();
|
||||||
|
|
||||||
const [task, setTask] = useState(null);
|
const [task, setTask] = useState(null);
|
||||||
|
|
||||||
@@ -138,6 +139,7 @@ export default function ViewTask() {
|
|||||||
if (!data) return;
|
if (!data) return;
|
||||||
setTask(data);
|
setTask(data);
|
||||||
});
|
});
|
||||||
|
fetchCompletions(taskListId, taskId, { page: 1, limit: 1 });
|
||||||
}, [taskListId, taskId]);
|
}, [taskListId, taskId]);
|
||||||
|
|
||||||
if (!task) return (
|
if (!task) return (
|
||||||
@@ -202,7 +204,7 @@ export default function ViewTask() {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
Deadline:{' '}
|
Deadline:{' '}
|
||||||
<span className="text-foreground font-medium">
|
<span className="text-foreground font-medium">
|
||||||
{fmtDate(task.deadline)}
|
{fmtDateTime(task.deadline)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -224,6 +226,28 @@ export default function ViewTask() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Completions */}
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<ClipboardCheck className="size-4" /> Completions
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
{completionLoading
|
||||||
|
? '…'
|
||||||
|
: `${completionPagination.totalRecords} submission${completionPagination.totalRecords === 1 ? '' : 's'}`}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => navigate(`/admin/taskList/${taskListId}/tasks/${taskId}/completions`)}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
{/* Requirements */}
|
{/* Requirements */}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function normalizeLinkUrl(url) {
|
|||||||
|
|
||||||
export const requirementSchema = z.object({
|
export const requirementSchema = z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
reference_id: z.string().optional(),
|
reference_id: z.string().nullable().optional(),
|
||||||
duration_seconds: z.number().optional(),
|
duration_seconds: z.number().optional(),
|
||||||
}).passthrough().superRefine((req, ctx) => {
|
}).passthrough().superRefine((req, ctx) => {
|
||||||
if (!READ_TYPES.includes(req.type)) return;
|
if (!READ_TYPES.includes(req.type)) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user