mirror of
https://github.com/soconnor0919/hristudio.git
synced 2026-02-04 23:46:32 -05:00
Begin plugins system
This commit is contained in:
@@ -50,9 +50,9 @@ interface TrialFormProps {
|
||||
export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
const router = useRouter();
|
||||
const { selectedStudyId } = useStudyContext();
|
||||
const contextStudyId = studyId || selectedStudyId;
|
||||
const contextStudyId = studyId ?? selectedStudyId;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isDeleting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const form = useForm<TrialFormData>({
|
||||
@@ -93,16 +93,36 @@ export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
// Set breadcrumbs
|
||||
const breadcrumbs = [
|
||||
{ label: "Dashboard", href: "/dashboard" },
|
||||
{ label: "Trials", href: "/trials" },
|
||||
...(mode === "edit" && trial
|
||||
{ label: "Studies", href: "/studies" },
|
||||
...(contextStudyId
|
||||
? [
|
||||
{
|
||||
label: `Trial ${trial.sessionNumber || trial.id.slice(-8)}`,
|
||||
href: `/trials/${trial.id}`,
|
||||
label: "Study",
|
||||
href: `/studies/${contextStudyId}`,
|
||||
},
|
||||
{ label: "Edit" },
|
||||
{ label: "Trials", href: `/studies/${contextStudyId}/trials` },
|
||||
...(mode === "edit" && trial
|
||||
? [
|
||||
{
|
||||
label: `Trial ${trial.sessionNumber || trial.id.slice(-8)}`,
|
||||
href: `/trials/${trial.id}`,
|
||||
},
|
||||
{ label: "Edit" },
|
||||
]
|
||||
: [{ label: "New Trial" }]),
|
||||
]
|
||||
: [{ label: "New Trial" }]),
|
||||
: [
|
||||
{ label: "Trials", href: "/trials" },
|
||||
...(mode === "edit" && trial
|
||||
? [
|
||||
{
|
||||
label: `Trial ${trial.sessionNumber || trial.id.slice(-8)}`,
|
||||
href: `/trials/${trial.id}`,
|
||||
},
|
||||
{ label: "Edit" },
|
||||
]
|
||||
: [{ label: "New Trial" }]),
|
||||
]),
|
||||
];
|
||||
|
||||
useBreadcrumbsEffect(breadcrumbs);
|
||||
@@ -112,13 +132,13 @@ export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
if (mode === "edit" && trial) {
|
||||
form.reset({
|
||||
experimentId: trial.experimentId,
|
||||
participantId: trial.participantId || "",
|
||||
participantId: trial?.participantId ?? "",
|
||||
scheduledAt: trial.scheduledAt
|
||||
? new Date(trial.scheduledAt).toISOString().slice(0, 16)
|
||||
: "",
|
||||
wizardId: trial.wizardId || undefined,
|
||||
notes: trial.notes || "",
|
||||
sessionNumber: trial.sessionNumber || 1,
|
||||
wizardId: trial.wizardId ?? undefined,
|
||||
notes: trial.notes ?? "",
|
||||
sessionNumber: trial.sessionNumber ?? 1,
|
||||
});
|
||||
}
|
||||
}, [trial, mode, form]);
|
||||
@@ -138,8 +158,8 @@ export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
participantId: data.participantId,
|
||||
scheduledAt: new Date(data.scheduledAt),
|
||||
wizardId: data.wizardId,
|
||||
sessionNumber: data.sessionNumber || 1,
|
||||
notes: data.notes || undefined,
|
||||
sessionNumber: data.sessionNumber ?? 1,
|
||||
notes: data.notes ?? undefined,
|
||||
});
|
||||
router.push(`/trials/${newTrial!.id}`);
|
||||
} else {
|
||||
@@ -147,8 +167,8 @@ export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
id: trialId!,
|
||||
scheduledAt: new Date(data.scheduledAt),
|
||||
wizardId: data.wizardId,
|
||||
sessionNumber: data.sessionNumber || 1,
|
||||
notes: data.notes || undefined,
|
||||
sessionNumber: data.sessionNumber ?? 1,
|
||||
notes: data.notes ?? undefined,
|
||||
});
|
||||
router.push(`/trials/${updatedTrial!.id}`);
|
||||
}
|
||||
@@ -244,7 +264,7 @@ export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
<SelectContent>
|
||||
{participantsData?.participants?.map((participant) => (
|
||||
<SelectItem key={participant.id} value={participant.id}>
|
||||
{participant.name || participant.participantCode} (
|
||||
{participant.name ?? participant.participantCode} (
|
||||
{participant.participantCode})
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -312,7 +332,7 @@ export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
<FormField>
|
||||
<Label htmlFor="wizardId">Assigned Wizard</Label>
|
||||
<Select
|
||||
value={form.watch("wizardId") || "none"}
|
||||
value={form.watch("wizardId") ?? "none"}
|
||||
onValueChange={(value) =>
|
||||
form.setValue("wizardId", value === "none" ? undefined : value)
|
||||
}
|
||||
@@ -329,11 +349,13 @@ export function TrialForm({ mode, trialId, studyId }: TrialFormProps) {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">No wizard assigned</SelectItem>
|
||||
{usersData?.map((user) => (
|
||||
<SelectItem key={user.id} value={user.id}>
|
||||
{user.name} ({user.email})
|
||||
</SelectItem>
|
||||
))}
|
||||
{usersData?.map(
|
||||
(user: { id: string; name: string; email: string }) => (
|
||||
<SelectItem key={user.id} value={user.id}>
|
||||
{user.name} ({user.email})
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
|
||||
@@ -58,6 +58,7 @@ export type Trial = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
participantCode?: string;
|
||||
};
|
||||
wizard: {
|
||||
id: string;
|
||||
@@ -119,7 +120,7 @@ function TrialActionsCell({ trial }: { trial: Trial }) {
|
||||
};
|
||||
|
||||
const handleCopyId = () => {
|
||||
navigator.clipboard.writeText(trial.id);
|
||||
void navigator.clipboard.writeText(trial.id);
|
||||
toast.success("Trial ID copied to clipboard");
|
||||
};
|
||||
|
||||
@@ -301,7 +302,7 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="ml-auto shrink-0 border-amber-200 bg-amber-50 text-amber-700"
|
||||
title={`Access restricted - You are an ${trial.userRole || "observer"} on this study`}
|
||||
title={`Access restricted - You are an ${trial.userRole ?? "observer"} on this study`}
|
||||
>
|
||||
{trial.userRole === "observer" ? "View Only" : "Restricted"}
|
||||
</Badge>
|
||||
@@ -317,9 +318,9 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
<DataTableColumnHeader column={column} title="Status" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const status = row.getValue("status") as Trial["status"];
|
||||
const status = row.getValue("status");
|
||||
const trial = row.original;
|
||||
const config = statusConfig[status];
|
||||
const config = statusConfig[status as keyof typeof statusConfig];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -343,7 +344,7 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
);
|
||||
},
|
||||
filterFn: (row, id, value: string[]) => {
|
||||
const status = row.getValue(id) as string;
|
||||
const status = row.getValue(id) as string; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
return value.includes(status);
|
||||
},
|
||||
},
|
||||
@@ -353,16 +354,22 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
<DataTableColumnHeader column={column} title="Participant" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const participant = row.getValue("participant") as Trial["participant"];
|
||||
const participant = row.original.participant;
|
||||
return (
|
||||
<div className="max-w-[120px]">
|
||||
<div className="flex items-center space-x-1">
|
||||
<User className="text-muted-foreground h-3 w-3 flex-shrink-0" />
|
||||
<span
|
||||
className="truncate text-sm font-medium"
|
||||
title={participant.name || "Unnamed Participant"}
|
||||
title={
|
||||
participant?.name ??
|
||||
participant?.participantCode ??
|
||||
"Unnamed Participant"
|
||||
}
|
||||
>
|
||||
{participant.name || "Unnamed Participant"}
|
||||
{participant?.name ??
|
||||
participant?.participantCode ??
|
||||
"Unnamed Participant"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -376,16 +383,16 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
<DataTableColumnHeader column={column} title="Experiment" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const experiment = row.getValue("experiment") as Trial["experiment"];
|
||||
const experiment = row.original.experiment;
|
||||
return (
|
||||
<div className="flex max-w-[140px] items-center space-x-2">
|
||||
<FlaskConical className="text-muted-foreground h-3 w-3 flex-shrink-0" />
|
||||
<Link
|
||||
href={`/experiments/${experiment.id}`}
|
||||
href={`/experiments/${experiment?.id ?? ""}`}
|
||||
className="truncate text-sm hover:underline"
|
||||
title={experiment.name || "Unnamed Experiment"}
|
||||
title={experiment?.name ?? "Unnamed Experiment"}
|
||||
>
|
||||
{experiment.name || "Unnamed Experiment"}
|
||||
{experiment?.name ?? "Unnamed Experiment"}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
@@ -402,7 +409,7 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
<DataTableColumnHeader column={column} title="Wizard" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const wizard = row.getValue("wizard") as Trial["wizard"];
|
||||
const wizard = row.original.wizard;
|
||||
if (!wizard) {
|
||||
return (
|
||||
<span className="text-muted-foreground text-sm">Not assigned</span>
|
||||
@@ -418,9 +425,9 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
</div>
|
||||
<div
|
||||
className="text-muted-foreground truncate text-xs"
|
||||
title={wizard.email}
|
||||
title={wizard.email ?? ""}
|
||||
>
|
||||
{wizard.email}
|
||||
{wizard.email ?? ""}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -437,7 +444,7 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
<DataTableColumnHeader column={column} title="Scheduled" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const date = row.getValue("scheduledAt") as Date | null;
|
||||
const date = row.getValue("scheduledAt") as Date | null; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
if (!date) {
|
||||
return (
|
||||
<span className="text-muted-foreground text-sm">Not scheduled</span>
|
||||
@@ -527,7 +534,7 @@ export const trialsColumns: ColumnDef<Trial>[] = [
|
||||
<DataTableColumnHeader column={column} title="Created" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const date = row.getValue("createdAt") as Date;
|
||||
const date = row.getValue("createdAt") as Date; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
return (
|
||||
<div className="text-sm whitespace-nowrap">
|
||||
{formatDistanceToNow(date, { addSuffix: true })}
|
||||
|
||||
@@ -59,10 +59,22 @@ export function TrialsDataTable() {
|
||||
return () => clearInterval(interval);
|
||||
}, [refetch]);
|
||||
|
||||
// Get study data for breadcrumbs
|
||||
const { data: studyData } = api.studies.get.useQuery(
|
||||
{ id: selectedStudyId! },
|
||||
{ enabled: !!selectedStudyId },
|
||||
);
|
||||
|
||||
// Set breadcrumbs
|
||||
useBreadcrumbsEffect([
|
||||
{ label: "Dashboard", href: "/dashboard" },
|
||||
{ label: "Trials" },
|
||||
{ label: "Studies", href: "/studies" },
|
||||
...(selectedStudyId && studyData
|
||||
? [
|
||||
{ label: studyData.name, href: `/studies/${selectedStudyId}` },
|
||||
{ label: "Trials" },
|
||||
]
|
||||
: [{ label: "Trials" }]),
|
||||
]);
|
||||
|
||||
// Transform trials data to match the Trial type expected by columns
|
||||
@@ -149,7 +161,7 @@ export function TrialsDataTable() {
|
||||
const filters = (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectTrigger className="h-8 w-[140px]">
|
||||
<SelectValue placeholder="Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -222,10 +234,10 @@ export function TrialsDataTable() {
|
||||
Limited Trial Access
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-amber-700">
|
||||
Some trials are marked as "View Only" or "Restricted" because
|
||||
you have observer-level access to their studies. Only
|
||||
researchers, wizards, and study owners can view detailed trial
|
||||
information.
|
||||
Some trials are marked as “View Only” or
|
||||
“Restricted” because you have observer-level
|
||||
access to their studies. Only researchers, wizards, and study
|
||||
owners can view detailed trial information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user