Pre-conf work 2025

This commit is contained in:
2025-09-02 08:25:41 -04:00
parent 550021a18e
commit 4acbec6288
75 changed files with 8047 additions and 5228 deletions

View File

@@ -1,43 +1,41 @@
"use client";
import {
Briefcase, Clock, GraduationCap, Info, Mail, Shield, User
} from "lucide-react";
import { Briefcase, Clock, GraduationCap, Info, Shield } from "lucide-react";
import { Avatar, AvatarFallback } from "~/components/ui/avatar";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
interface ParticipantInfoProps {
participant: {
id: string;
participantCode: string;
email: string | null;
name: string | null;
demographics: any;
demographics: Record<string, unknown> | null;
};
trialStatus: "scheduled" | "in_progress" | "completed" | "aborted" | "failed";
}
export function ParticipantInfo({ participant }: ParticipantInfoProps) {
const demographics = participant.demographics || {};
export function ParticipantInfo({
participant,
trialStatus: _trialStatus,
}: ParticipantInfoProps) {
const demographics = participant.demographics ?? {};
// Extract common demographic fields
const age = demographics.age;
const gender = demographics.gender;
const occupation = demographics.occupation;
const education = demographics.education;
const language = demographics.primaryLanguage || demographics.language;
const location = demographics.location || demographics.city;
const experience = demographics.robotExperience || demographics.experience;
const age = demographics.age as string | number | undefined;
const gender = demographics.gender as string | undefined;
const occupation = demographics.occupation as string | undefined;
const education = demographics.education as string | undefined;
const language =
(demographics.primaryLanguage as string | undefined) ??
(demographics.language as string | undefined);
const experience =
(demographics.robotExperience as string | undefined) ??
(demographics.experience as string | undefined);
// Get participant initials for avatar
const getInitials = () => {
if (participant.name) {
const nameParts = participant.name.split(" ");
return nameParts.map((part) => part.charAt(0).toUpperCase()).join("");
}
return participant.participantCode.substring(0, 2).toUpperCase();
};
const formatDemographicValue = (key: string, value: any) => {
const formatDemographicValue = (key: string, value: unknown) => {
if (value === null || value === undefined || value === "") return null;
// Handle different data types
@@ -53,81 +51,64 @@ export function ParticipantInfo({ participant }: ParticipantInfoProps) {
return JSON.stringify(value);
}
return String(value);
return typeof value === "string" ? value : JSON.stringify(value);
};
return (
<div className="space-y-4">
<div className="flex items-center space-x-2">
<User className="h-4 w-4 text-slate-600" />
<h3 className="font-medium text-slate-900">Participant</h3>
</div>
{/* Basic Info Card */}
<Card className="shadow-sm">
<CardContent className="p-4">
<div className="flex items-start space-x-3">
<Avatar className="h-10 w-10">
<AvatarFallback className="bg-blue-100 font-medium text-blue-600">
{getInitials()}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<div className="truncate font-medium text-slate-900">
{participant.name || "Anonymous"}
</div>
<div className="text-sm text-slate-600">
ID: {participant.participantCode}
</div>
{participant.email && (
<div className="mt-1 flex items-center space-x-1 text-xs text-slate-500">
<Mail className="h-3 w-3" />
<span className="truncate">{participant.email}</span>
</div>
)}
{/* Basic Info */}
<div className="rounded-lg border p-4">
<div className="flex items-start space-x-3">
<Avatar className="h-10 w-10">
<AvatarFallback className="font-medium">
{getInitials()}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<div className="truncate font-medium text-slate-900">
Participant {participant.participantCode}
</div>
<div className="text-sm text-slate-600">
ID: {participant.participantCode}
</div>
</div>
</CardContent>
</Card>
</div>
</div>
{/* Quick Demographics */}
{(age || gender || language) && (
<Card className="shadow-sm">
<CardContent className="p-4">
<div className="grid grid-cols-1 gap-2 text-sm">
{age && (
<div className="flex items-center justify-between">
<span className="text-slate-600">Age:</span>
<span className="font-medium">{age}</span>
</div>
)}
{gender && (
<div className="flex items-center justify-between">
<span className="text-slate-600">Gender:</span>
<span className="font-medium capitalize">{gender}</span>
</div>
)}
{language && (
<div className="flex items-center justify-between">
<span className="text-slate-600">Language:</span>
<span className="font-medium">{language}</span>
</div>
)}
</div>
</CardContent>
</Card>
{(age ?? gender ?? language) && (
<div className="rounded-lg border p-4">
<div className="grid grid-cols-1 gap-2 text-sm">
{age && (
<div className="flex items-center justify-between">
<span className="text-slate-600">Age:</span>
<span className="font-medium">{age}</span>
</div>
)}
{gender && (
<div className="flex items-center justify-between">
<span className="text-slate-600">Gender:</span>
<span className="font-medium capitalize">{gender}</span>
</div>
)}
{language && (
<div className="flex items-center justify-between">
<span className="text-slate-600">Language:</span>
<span className="font-medium">{language}</span>
</div>
)}
</div>
</div>
)}
{/* Background Info */}
{(occupation || education || experience) && (
<Card className="shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="flex items-center space-x-1 text-sm font-medium text-slate-700">
<Info className="h-3 w-3" />
<span>Background</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-2 pt-0">
{(occupation ?? education ?? experience) && (
<div className="rounded-lg border p-4">
<div className="mb-3 flex items-center space-x-1 text-sm font-medium text-slate-700">
<Info className="h-3 w-3" />
<span>Background</span>
</div>
<div className="space-y-2">
{occupation && (
<div className="flex items-start space-x-2 text-sm">
<Briefcase className="mt-0.5 h-3 w-3 flex-shrink-0 text-slate-400" />
@@ -155,19 +136,17 @@ export function ParticipantInfo({ participant }: ParticipantInfoProps) {
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
)}
{/* Additional Demographics */}
{Object.keys(demographics).length > 0 && (
<Card className="shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-slate-700">
Additional Info
</CardTitle>
</CardHeader>
<CardContent className="pt-0">
<div className="rounded-lg border p-4">
<div className="mb-3 text-sm font-medium text-slate-700">
Additional Info
</div>
<div>
<div className="space-y-1">
{Object.entries(demographics)
.filter(
@@ -211,30 +190,26 @@ export function ParticipantInfo({ participant }: ParticipantInfoProps) {
);
})}
</div>
</CardContent>
</Card>
</div>
</div>
)}
{/* Consent Status */}
<Card className="border-green-200 bg-green-50 shadow-sm">
<CardContent className="p-3">
<div className="flex items-center space-x-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-sm font-medium text-green-800">
Consent Verified
</span>
</div>
<div className="mt-1 text-xs text-green-600">
Participant has provided informed consent
</div>
</CardContent>
</Card>
<div className="rounded-lg border p-3">
<div className="flex items-center space-x-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-sm font-medium">Consent Verified</span>
</div>
<div className="text-muted-foreground mt-1 text-xs">
Participant has provided informed consent
</div>
</div>
{/* Session Info */}
<div className="space-y-1 text-xs text-slate-500">
<div className="flex items-center space-x-1">
<Clock className="h-3 w-3" />
<span>Session started: {new Date().toLocaleTimeString()}</span>
<span>Session active</span>
</div>
</div>
</div>