Technical Specification for Clinic Demo UI
1. Demo Page State Management
// Demo page state interface
interface DemoPageState {
// File upload state
selectedFile : File | null ;
isUploading : boolean ;
// Analysis state
analysis : ClinicAnalysis | null ;
analysisError : string | null ;
// UI state
isSampleMode : boolean ;
}
interface UploadPanelProps {
onAnalysisStart : ( ) => void ;
onAnalysisComplete : ( analysis : ClinicAnalysis ) => void ;
onError : ( error : string ) => void ;
disabled ?: boolean ;
}
// Internal states
enum UploadState {
IDLE = 'idle' ,
DRAGGING = 'dragging' ,
UPLOADING = 'uploading' ,
ERROR = 'error'
}
3. AnalysisHeader Component
interface AnalysisHeaderProps {
analysis : ClinicAnalysis | null ;
isLoading ?: boolean ;
}
// Display format for date
const formatDate = ( isoString : string ) : string => {
// Format: "2024年1月15日 10:30"
} ;
4. StepsTimeline Component
interface StepsTimelineProps {
steps : ProcessStep [ ] ;
isLoading ?: boolean ;
}
// Timeline item display
interface TimelineItem {
step : ProcessStep ;
isHighlighted ?: boolean ;
}
5. WarningsList Component
interface WarningsListProps {
warnings : string [ ] | null ;
isLoading ?: boolean ;
}
// Regular file upload
const uploadFileForAnalysis = async ( file : File ) : Promise < ClinicAnalysis > => {
const formData = new FormData ( ) ;
formData . append ( 'file' , file ) ;
formData . append ( 'caseKind' , 'general' ) ;
const response = await fetch ( '/api/clinic/analyze' , {
method : 'POST' ,
body : formData
} ) ;
if ( ! response . ok ) {
throw new Error ( await response . text ( ) ) ;
}
const result = await response . json ( ) ;
return result . analysis ;
} ;
// Sample mode API call
const getSampleAnalysis = async ( ) : Promise < ClinicAnalysis > => {
const response = await fetch ( '/api/clinic/analyze?sample=true' , {
method : 'POST' ,
body : new FormData ( ) // Empty form data for sample mode
} ) ;
if ( ! response . ok ) {
throw new Error ( await response . text ( ) ) ;
}
const result = await response . json ( ) ;
return result . analysis ;
} ;
interface ApiError {
error : string ;
details ?: string ;
}
const handleApiError = ( error : ApiError ) : string => {
// Map specific error codes to user-friendly messages
if ( error . error . includes ( 'No file provided' ) ) {
return '請選擇要上傳的文件' ;
}
if ( error . error . includes ( 'File too large' ) ) {
return '文件大小超過限制(最大 15MB)' ;
}
if ( error . error . includes ( 'Invalid file type' ) ) {
return '不支持的文件類型,請上傳圖片或 PDF' ;
}
return '分析時發生錯誤,請稍後再試' ;
} ;
UI Component Specifications
1. Input Component Variants
interface InputProps {
type ?: 'text' | 'email' | 'password' | 'file' ;
variant ?: 'default' | 'file' | 'drag-drop' ;
placeholder ?: string ;
accept ?: string ; // For file inputs
multiple ?: boolean ;
disabled ?: boolean ;
error ?: string ;
onDragEnter ?: ( e : React . DragEvent ) => void ;
onDragLeave ?: ( e : React . DragEvent ) => void ;
onDrop ?: ( e : React . DragEvent ) => void ;
onChange ?: ( e : React . ChangeEvent < HTMLInputElement > ) => void ;
}
2. Alert Component Variants
interface AlertProps {
variant ?: 'info' | 'warning' | 'error' | 'success' ;
title ?: string ;
description ?: string ;
children ?: React . ReactNode ;
dismissible ?: boolean ;
onDismiss ?: ( ) => void ;
}
interface SpinnerProps {
size ?: 'sm' | 'md' | 'lg' ;
color ?: 'brand' | 'accent' | 'muted' ;
label ?: string ; // Accessibility label
}
Motion Animation Specifications
1. Page Transition Animations
// Fade in animation for the entire demo page
const pageTransition = {
initial : { opacity : 0 , y : 20 } ,
animate : { opacity : 1 , y : 0 } ,
transition : { duration : 0.4 , ease : "easeOut" }
} ;
// Stagger animation for timeline items
const containerVariants = {
hidden : { opacity : 0 } ,
show : {
opacity : 1 ,
transition : {
staggerChildren : 0.1
}
}
} ;
const itemVariants = {
hidden : { opacity : 0 , y : 20 } ,
show : { opacity : 1 , y : 0 }
} ;
2. Loading State Animations
// Shimmer effect for loading states
const shimmerVariants = {
initial : { x : - 100 } ,
animate : {
x : 100 ,
transition : {
repeat : Infinity ,
duration : 1.5 ,
ease : "easeInOut"
}
}
} ;
Responsive Design Breakpoints
// Breakpoint configuration
const breakpoints = {
sm : '640px' , // Mobile
md : '768px' , // Tablet
lg : '1024px' , // Desktop
xl : '1280px' // Large Desktop
} ;
// Layout configurations
const layoutConfig = {
mobile : {
uploadPanel : { width : '100%' , order : 1 } ,
resultsPanel : { width : '100%' , order : 2 }
} ,
desktop : {
uploadPanel : { width : '45%' , order : 1 } ,
resultsPanel : { width : '55%' , order : 2 }
}
} ;
// Example test structure for UploadPanel
describe ( 'UploadPanel' , ( ) => {
it ( 'renders file input and submit button' , ( ) => {
// Test basic component rendering
} ) ;
it ( 'shows loading state during upload' , ( ) => {
// Test loading state UI
} ) ;
it ( 'displays error message on upload failure' , ( ) => {
// Test error handling UI
} ) ;
it ( 'handles file selection correctly' , ( ) => {
// Test file selection logic
} ) ;
it ( 'supports drag and drop' , ( ) => {
// Test drag and drop functionality
} ) ;
} ) ;
// Example test for demo page
describe ( 'Clinic Demo Page' , ( ) => {
it ( 'renders without crashing' , ( ) => {
// Smoke test
} ) ;
it ( 'handles complete upload flow' , async ( ) => {
// Test full user journey
} ) ;
it ( 'handles sample mode correctly' , async ( ) => {
// Test sample mode functionality
} ) ;
} ) ;
Performance Considerations
1. File Upload Optimization
// File size validation before upload
const validateFile = ( file : File ) : { valid : boolean ; error ?: string } => {
const MAX_SIZE = 15 * 1024 * 1024 ; // 15MB
const ALLOWED_TYPES = [ 'image/jpeg' , 'image/png' , 'application/pdf' ] ;
if ( file . size > MAX_SIZE ) {
return { valid : false , error : '文件過大' } ;
}
if ( ! ALLOWED_TYPES . includes ( file . type ) ) {
return { valid : false , error : '不支持的文件類型' } ;
}
return { valid : true } ;
} ;
// Clean up object URLs to prevent memory leaks
const cleanupObjectUrl = ( url : string | null ) => {
if ( url ) {
URL . revokeObjectURL ( url ) ;
}
} ;
Accessibility Requirements
All interactive elements must be keyboard accessible
Tab order should follow logical flow
Focus indicators must be visible
Semantic HTML structure
ARIA labels for custom components
Announcements for state changes
All text must meet WCAG AA contrast requirements
Interactive elements must have sufficient contrast
Color should not be the only indicator of state