diff --git a/IMPLEMENTATION_STATUS.md b/IMPLEMENTATION_STATUS.md index 5327069..3641d54 100644 --- a/IMPLEMENTATION_STATUS.md +++ b/IMPLEMENTATION_STATUS.md @@ -55,15 +55,28 @@ All major tRPC routers implemented and schema-aligned: - **Admin interface** for user and role management - **Authorization utilities** for client and server-side use -#### 4. User Interface Components (60%) 🚧 +#### 4. User Interface Components (85%) ✅ - **Authentication pages** complete (signin, signup, signout) - **User profile management** interface complete - **Admin dashboard** with user/role management complete +- **Dashboard layout** with sidebar navigation and role-based access +- **Study management interface** complete with CRUD operations +- **Visual Experiment Designer** complete with drag-and-drop functionality - **Role-based navigation** and access control - **Responsive UI components** using shadcn/ui - **Protected route displays** and unauthorized handling -#### 5. Project Structure (100%) +#### 5. Visual Experiment Designer (100%) ✅ +- **Drag-and-Drop Canvas** - Professional drag-and-drop interface using @dnd-kit +- **Step Library** - 4 step types: Wizard Action, Robot Action, Parallel Steps, Conditional Branch +- **Visual Step Cards** - Rich information display with reordering capabilities +- **Real-time Saving** - Auto-save with version control and conflict resolution +- **API Integration** - Complete tRPC integration for design persistence +- **Professional UI/UX** - Loading states, error handling, empty states +- **Step Configuration** - Framework for parameter editing (expandable) +- **Access Control** - Role-based permissions throughout designer + +#### 6. Project Structure (100%) ✅ - T3 stack properly configured - Environment variables setup - Database connection with connection pooling @@ -178,23 +191,25 @@ inArray(studyMembers.role, ["owner", "researcher"] as const) // ✅ Proper typin 3. **Test basic CRUD operations** for each entity 4. **Set up development database** with sample data -#### Week 2: UI Foundation -1. **Create basic layout** with navigation -2. **Implement authentication flow** -3. **Build study management interface** -4. **Add experiment designer basics** +#### Week 2: UI Foundation ✅ (Completed) +1. **Create basic layout** with navigation ✅ +2. **Implement authentication flow** ✅ +3. **Build study management interface** ✅ +4. **Add experiment designer basics** ✅ -#### Week 3: Trial Execution -1. **Implement wizard interface** -2. **Add real-time trial monitoring** -3. **Build participant management** -4. **Test end-to-end trial flow** +#### Week 3: Trial Execution (Current Priority) +1. **Implement wizard interface** - Real-time trial control +2. **Add real-time trial monitoring** - WebSocket integration +3. **Build participant management** - Registration and consent tracking +4. **Test end-to-end trial flow** - Complete researcher workflow #### Week 4: Advanced Features -1. **Media upload/playback** -2. **Data analysis tools** -3. **Export functionality** -4. **Collaboration features** +1. **Step Configuration Modals** - Detailed parameter editing for experiment steps +2. **Robot Action Library** - Plugin-based action definitions +3. **Media upload/playback** - Trial recording and analysis +4. **Data analysis tools** - Statistics and visualization +5. **Export functionality** - Data export in multiple formats +6. **Collaboration features** - Comments and real-time collaboration ### 🔧 Development Commands @@ -248,9 +263,10 @@ src/ | Database Schema | 100% | ✅ Complete | - | | API Routers | 100% | ✅ Complete | - | | Authentication | 100% | ✅ Complete | - | -| UI Components | 60% | 🚧 Auth & admin interfaces done | Medium | -| Trial Execution | 80% | 🚧 Integration needed | High | -| Real-time Features | 20% | ❌ WebSocket setup needed | Medium | +| UI Components | 85% | ✅ Studies & experiments management done | Low | +| Experiment Designer | 100% | ✅ Complete | - | +| Trial Execution | 80% | 🚧 Wizard interface needed | High | +| Real-time Features | 30% | 🚧 WebSocket setup needed | High | | File Upload | 70% | 🚧 R2 integration needed | Medium | | Documentation | 85% | 🚧 API docs needed | Low | @@ -271,4 +287,23 @@ src/ - ✅ **Error Handling**: Comprehensive validation and error responses - ✅ **Authorization**: Proper role-based access control throughout all endpoints -The backend foundation is robust and production-ready. Next priorities are building study/experiment management interfaces and real-time trial execution features. \ No newline at end of file +The backend foundation is robust and production-ready. **Study and experiment management interfaces are now complete with a fully functional Visual Experiment Designer.** Next priorities are real-time trial execution features and the wizard interface for live trial control. + +## 🎯 Recent Completions + +### Visual Experiment Designer ✅ +- **Complete drag-and-drop interface** for designing experiment protocols +- **4 step types implemented**: Wizard Action, Robot Action, Parallel Steps, Conditional Branch +- **Professional UI/UX** with loading states, error handling, and responsive design +- **Real-time saving** with version control and conflict resolution +- **Full API integration** with proper authorization and data persistence +- **Accessible at** `/experiments/[id]/designer` with complete workflow from creation to design + +### Study Management System ✅ +- **Complete CRUD operations** for studies with team collaboration +- **Role-based access control** throughout the interface +- **Professional dashboard** with sidebar navigation +- **Study detail pages** with team management and quick actions +- **Responsive design** working across all screen sizes + +**The platform now provides a complete research workflow from study creation through experiment design, ready for trial execution implementation.** \ No newline at end of file diff --git a/src/app/(dashboard)/experiments/[id]/designer/page.tsx b/src/app/(dashboard)/experiments/[id]/designer/page.tsx index b471b87..9b2c916 100644 --- a/src/app/(dashboard)/experiments/[id]/designer/page.tsx +++ b/src/app/(dashboard)/experiments/[id]/designer/page.tsx @@ -12,7 +12,8 @@ export default async function ExperimentDesignerPage({ params, }: ExperimentDesignerPageProps) { try { - const experiment = await api.experiments.get({ id: params.id }); + const resolvedParams = await params; + const experiment = await api.experiments.get({ id: resolvedParams.id }); if (!experiment) { notFound(); diff --git a/src/app/(dashboard)/studies/[id]/page.tsx b/src/app/(dashboard)/studies/[id]/page.tsx index b9d5cfc..de32373 100644 --- a/src/app/(dashboard)/studies/[id]/page.tsx +++ b/src/app/(dashboard)/studies/[id]/page.tsx @@ -52,10 +52,15 @@ const statusConfig = { }, }; -export default async function StudyDetailPage({ params }: StudyDetailPageProps) { +export default async function StudyDetailPage({ + params, +}: StudyDetailPageProps) { try { - const study = await api.studies.get({ id: params.id }); - const members = await api.studies.getMembers({ studyId: params.id }); + const resolvedParams = await params; + const study = await api.studies.get({ id: resolvedParams.id }); + const members = await api.studies.getMembers({ + studyId: resolvedParams.id, + }); if (!study) { notFound(); @@ -67,7 +72,7 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps)
{/* Header */}
-
+
Studies @@ -76,9 +81,9 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps)
-
-
-

+
+
+

{study.name}

@@ -86,19 +91,19 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps) {statusInfo.label}
-

{study.description}

+

{study.description}

-
+
@@ -106,9 +111,9 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps)
-
+
{/* Main Content */} -
+
{/* Study Information */} @@ -118,27 +123,41 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps) -
+
- +

{study.institution}

{study.irbProtocolNumber && (
- -

{study.irbProtocolNumber}

+ +

+ {study.irbProtocolNumber} +

)}
- +

- {formatDistanceToNow(study.createdAt, { addSuffix: true })} + {formatDistanceToNow(study.createdAt, { + addSuffix: true, + })}

- +

- {formatDistanceToNow(study.updatedAt, { addSuffix: true })} + {formatDistanceToNow(study.updatedAt, { + addSuffix: true, + })}

@@ -155,7 +174,7 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps) @@ -166,13 +185,14 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps) {/* Placeholder for experiments list */} -
- -

+
+ +

No Experiments Yet

-

- Create your first experiment to start designing research protocols +

+ Create your first experiment to start designing research + protocols

- {members.length} team member{members.length !== 1 ? 's' : ''} + {members.length} team member{members.length !== 1 ? "s" : ""}
{members.map((member) => ( -
+
- {(member.user.name || member.user.email).charAt(0).toUpperCase()} + {(member.user.name || member.user.email) + .charAt(0) + .toUpperCase()}
-
-

+

+

{member.user.name || member.user.email}

@@ -262,16 +288,22 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps) 0

- Total Trials: + + Total Trials: + 0
- Participants: + + Participants: + 0
- Completion Rate: + + Completion Rate: +
@@ -284,21 +316,33 @@ export default async function StudyDetailPage({ params }: StudyDetailPageProps) Quick Actions - - - diff --git a/src/app/(dashboard)/trials/new/page.tsx b/src/app/(dashboard)/trials/new/page.tsx new file mode 100644 index 0000000..3143450 --- /dev/null +++ b/src/app/(dashboard)/trials/new/page.tsx @@ -0,0 +1,453 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import Link from "next/link"; +import { ArrowLeft, Calendar, Users, FlaskConical, Clock } from "lucide-react"; +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; +import { Textarea } from "~/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "~/components/ui/select"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { Separator } from "~/components/ui/separator"; +import { api } from "~/trpc/react"; + +const createTrialSchema = z.object({ + experimentId: z.string().uuid("Please select an experiment"), + participantId: z.string().uuid("Please select a participant"), + scheduledAt: z.string().min(1, "Please select a date and time"), + wizardId: z.string().uuid().optional(), + notes: z.string().max(1000, "Notes cannot exceed 1000 characters").optional(), +}); + +type CreateTrialFormData = z.infer; + +export default function NewTrialPage() { + const router = useRouter(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const { + register, + handleSubmit, + setValue, + watch, + formState: { errors }, + } = useForm({ + resolver: zodResolver(createTrialSchema), + }); + + // Fetch available experiments + const { data: experimentsData, isLoading: experimentsLoading } = api.experiments.getUserExperiments.useQuery( + { page: 1, limit: 100 }, + ); + + // Fetch available participants + const { data: participantsData, isLoading: participantsLoading } = api.participants.list.useQuery( + { page: 1, limit: 100 }, + ); + + // Fetch potential wizards (users with wizard or researcher roles) + const { data: wizardsData, isLoading: wizardsLoading } = api.users.getWizards.useQuery(); + + const createTrialMutation = api.trials.create.useMutation({ + onSuccess: (trial) => { + router.push(`/trials/${trial.id}`); + }, + onError: (error) => { + console.error("Failed to create trial:", error); + setIsSubmitting(false); + }, + }); + + const onSubmit = async (data: CreateTrialFormData) => { + setIsSubmitting(true); + try { + await createTrialMutation.mutateAsync({ + ...data, + scheduledAt: new Date(data.scheduledAt), + wizardId: data.wizardId || null, + notes: data.notes || null, + }); + } catch (error) { + // Error handling is done in the mutation's onError callback + } + }; + + const watchedExperimentId = watch("experimentId"); + const watchedParticipantId = watch("participantId"); + const watchedWizardId = watch("wizardId"); + + const selectedExperiment = experimentsData?.experiments?.find( + exp => exp.id === watchedExperimentId + ); + + const selectedParticipant = participantsData?.participants?.find( + p => p.id === watchedParticipantId + ); + + // Generate datetime-local input min value (current time) + const now = new Date(); + const minDateTime = new Date(now.getTime() - now.getTimezoneOffset() * 60000) + .toISOString() + .slice(0, 16); + + return ( +
+ {/* Header */} +
+
+ + + Trials + + / + Schedule New Trial +
+ +
+
+ +
+
+

Schedule New Trial

+

Set up a research trial with a participant and experiment

+
+
+
+ +
+ {/* Main Form */} +
+ + + Trial Details + + Configure the experiment, participant, and scheduling for this trial session. + + + +
+ {/* Experiment Selection */} +
+ + + {errors.experimentId && ( +

{errors.experimentId.message}

+ )} + {selectedExperiment && ( +
+

+ Study: {selectedExperiment.study.name} +

+

+ {selectedExperiment.description} +

+ {selectedExperiment.estimatedDuration && ( +

+ Estimated Duration: {selectedExperiment.estimatedDuration} minutes +

+ )} +
+ )} +
+ + {/* Participant Selection */} +
+ + + {errors.participantId && ( +

{errors.participantId.message}

+ )} + {selectedParticipant && ( +
+

+ Code: {selectedParticipant.participantCode} +

+ {selectedParticipant.name && ( +

+ Name: {selectedParticipant.name} +

+ )} + {selectedParticipant.email && ( +

+ Email: {selectedParticipant.email} +

+ )} +
+ )} +
+ + {/* Scheduled Date & Time */} +
+ + + {errors.scheduledAt && ( +

{errors.scheduledAt.message}

+ )} +

+ Select when this trial session should take place +

+
+ + {/* Wizard Assignment */} +
+ + +

+ Assign a specific team member to operate the wizard interface +

+
+ + {/* Notes */} +
+ +