Skip to content

Latest commit

 

History

History
358 lines (284 loc) · 7.2 KB

File metadata and controls

358 lines (284 loc) · 7.2 KB

Technical Specification for Clinic Demo UI

Component Interfaces

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;
}

2. UploadPanel Component

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;
}

API Integration

1. File Upload API Call

// 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;
};

2. Error Handling

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;
}

3. Spinner Component

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 }
  }
};

Testing Strategy

1. Component Testing

// 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
  });
});

2. Integration Testing

// 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 };
};

2. Memory Management

// Clean up object URLs to prevent memory leaks
const cleanupObjectUrl = (url: string | null) => {
  if (url) {
    URL.revokeObjectURL(url);
  }
};

Accessibility Requirements

1. Keyboard Navigation

  • All interactive elements must be keyboard accessible
  • Tab order should follow logical flow
  • Focus indicators must be visible

2. Screen Reader Support

  • Semantic HTML structure
  • ARIA labels for custom components
  • Announcements for state changes

3. Color Contrast

  • All text must meet WCAG AA contrast requirements
  • Interactive elements must have sufficient contrast
  • Color should not be the only indicator of state