# Quick Reference: Frontend Component Architecture

## File Locations - Quick Lookup

### Client-Side Components (Your Repo)
```
/Users/super/Documents/GitHub/things-factory/packages/dataset/client/
├── components/
│   ├── data-entry-form.ts          - Form wrapper using ox-data-entry-form
│   └── checklist-entry-form.ts     - Checklist wrapper using ox-checklist-entry-form
└── activities/
    └── activity-data-collect-edit.ts - Workflow activity entry point
```

### Server-Side Types & Utils (Your Repo)
```
/Users/super/Documents/GitHub/things-factory/packages/dataset/server/
├── service/data-set/
│   └── data-item-type.ts           - DataItemType enum + media options schema
└── utils/
    └── media-validation.ts         - Media file validation utilities
```

### Core Components (Dependency - Node Modules)
```
@operato/dataset (in node_modules)
├── ox-data-entry-form.js          - Main form component
├── ox-data-entry-subgroup-form.js - Grouped items in Grist table
└── ox-data-input-factory.js        - Type-to-component mapping factory

@operato/input (in node_modules)
├── ox-input-file.js               - File upload component
├── ox-input-image.js              - Image upload component
├── ox-input-signature.js           - Digital signature component
└── ox-form-field.js               - Base class for all input components
```

---

## How Data Entry Works - 5 Step Flow

### Step 1: User Opens Activity
File: `activity-data-collect-edit.ts`
- Gets `dataSetId` from workflow
- Fetches DataSet with dataItems via GraphQL

### Step 2: OxDataEntryForm Renders
File: `@operato/dataset/ox-data-entry-form.js`
- Iterates through `dataItems.filter(item => item.active)`
- For grouped items: renders `ox-data-entry-subgroup-form`
- For non-grouped: calls `OxDataInputFactory.createInputElement()`

### Step 3: OxDataInputFactory Creates Input
File: `@operato/dataset/ox-data-input-factory.js`
- Switch statement on `dataItem.type`
- Currently supports: text, number, boolean, date, datetime, select, radio, file, signature
- **MISSING:** image, video, audio

### Step 4: User Submits Form
- Form collects values from all inputs by `name` attribute
- `buildValue()` extracts values into object: `{ [tag]: any }`
- GraphQL mutation sends with `context: { hasUpload: true }`

### Step 5: Server Processes Files
File: `create-data-sample.ts`
- Iterates `dataItems` looking for `isFileOrMediaType()`
- If media type: validates with `validateMediaFile()`
- Creates attachments and stores file paths in data

---

## Type Mapping (OxDataInputFactory)

### Current Types
```
'select'    → <select name=${tag}> with options
'radio'     → <label><input type="radio"> with options
'boolean'   → <input type="checkbox">
'number'    → <input type="number">
'date'      → <input type="date">
'datetime'  → <input type="datetime-local">
'file'      → <ox-input-file multiple>
'signature' → <ox-input-signature>
'text'      → <input type="text"> (default)
```

### Missing Types (Need Implementation)
```
'image'     → <ox-input-image>      (TO BE CREATED)
'video'     → <ox-input-video>      (TO BE CREATED)
'audio'     → <ox-input-audio>      (TO BE CREATED)
```

---

## Key Functions & Classes

### OxFormField (Base Class)
**Properties:**
- `name: string` - Form field identifier
- `value: T` - Field value (generic)
- `disabled: boolean` - Disabled state

**Methods:**
- `connectedCallback()` - Register with parent form
- `appendFormData(e: FormDataEvent)` - Add to form submission

### OxDataEntryForm
**Key Methods:**
- `buildInputs()` - Generate all input elements
- `buildValue()` - Extract form values as object
- `extractValuesFromInputs()` - Query DOM by name attribute

### OxDataInputFactory
**Static Methods:**
- `createInputElement(type, tag, value, options, idx)` - Route to correct component
- `mapGristType(type)` - Convert to Grist column type
- `getGristRecordOptions(type, options)` - Grid cell configuration

### Media Validation Utils
**Functions:**
- `validateMediaFile(file, mediaType, options)` - Main validator
- `isValidMimeType(mediaType, mimetype)` - Check MIME type
- `isAcceptedFormat(filename, acceptFormats)` - Check extension
- `isValidFileSize(fileSize, maxSize)` - Check size
- `isMediaType(type)` - Check if image/video/audio

---

## Adding New Media Components - Checklist

### 1. Create Component File
```
Location: packages/dataset/client/components/ox-input-image.ts
Extend: OxFormField
Pattern: Similar to ox-input-file
Methods needed:
  - render()
  - updated()
  - firstUpdated()
  - _onChangeValue()
  - _notifyChange()
```

### 2. Update OxDataInputFactory
File: Update @operato/dataset source (or create wrapper)
Add to switch:
```javascript
case 'image': return html`<ox-input-image ...>`;
case 'video': return html`<ox-input-video ...>`;
case 'audio': return html`<ox-input-audio ...>`;
```

### 3. Add to mapGristType()
```javascript
case 'image': return 'image';
case 'video': return 'video';
case 'audio': return 'audio';
```

### 4. Add to getGristRecordOptions()
```javascript
case 'image':
case 'video':
case 'audio':
  return { multiple: options.multiple || false };
```

### 5. Import in Components
```typescript
import '@operato/dataset/ox-input-image.js'
import '@operato/dataset/ox-input-video.js'
import '@operato/dataset/ox-input-audio.js'
```

---

## Component Value Extraction (Important!)

### How Form Gets Values
```typescript
// In ox-data-entry-form.js, line ~89
const editors = Array.from(
  this.renderRoot.querySelectorAll(`[name=${tag}]`)
)
// Extracts value from: name="product_image"
```

### Why Name Attribute Matters
- Form values keyed by `name` attribute
- Must match `dataItem.tag` value
- All OxInput* components must set name on native input

### File Upload Special Handling
Files passed as: `[{ file: FileUpload }, ...]`
Backend converts to: `[{ id, mimetype, name, fullpath }, ...]`

---

## DataItem Configuration Examples

### Image Field Config
```typescript
{
  name: 'Product Image',
  tag: 'product_image',        // ← Used as name attribute
  type: 'image',               // ← Routes to OxInputImage
  description: 'Upload product photo',
  unit: 'JPG/PNG',
  quota: 3,                    // Allow up to 3 images
  options: {
    accept: ['jpeg', 'png'],
    maxSize: 10485760,         // 10MB
    maxWidth: 4096,
    maxHeight: 4096,
    multiple: true
  }
}
```

### Video Field Config
```typescript
{
  name: 'Demo Video',
  tag: 'demo_video',
  type: 'video',
  quota: 1,
  options: {
    accept: ['mp4', 'webm'],
    maxSize: 104857600,        // 100MB
    maxDuration: 300,          // 5 minutes
    multiple: false
  }
}
```

### Audio Field Config
```typescript
{
  name: 'Voice Note',
  tag: 'voice_note',
  type: 'audio',
  quota: 1,
  options: {
    accept: ['mp3', 'wav'],
    maxSize: 52428800,         // 50MB
    maxDuration: 600,          // 10 minutes
    multiple: false
  }
}
```

---

## File Upload GraphQL Context

### Why `hasUpload: true`?
```typescript
// In data-entry-form.ts
context: {
  hasUpload: true
}
```
This tells Apollo Client to:
1. Use `multipart/form-data` encoding (not JSON)
2. Include files in GraphQL variables
3. Stream files to server

### GraphQL Variable Structure
```typescript
{
  dataSample: {
    dataSet: { id: '...' },
    data: {
      product_image: [{file: FileUpload}, ...],
      demo_video: [{file: FileUpload}],
      ...
    }
  }
}
```

---

## Common Gotchas

### 1. OxInputImage vs OxInputFile
- **File**: Generic file upload
- **Image**: Image-specific with preview/validation
- Note: OxInputImage exists in @operato/input but not used in factory

### 2. Name Attribute is Critical
```typescript
// CORRECT - matches dataItem.tag
<ox-input-image name="product_image" ...></ox-input-image>

// WRONG - value won't be extracted
<ox-input-image ...></ox-input-image>
```

### 3. Quota vs Multiple
```typescript
quota: 3, options: { multiple: true }
// Allows user to select 3 times, each time multiple files

quota: 1, options: { multiple: false }
// Single file selection
```

### 4. Group vs Subgroup
```typescript
group: 'measurements'     // Groups items in table (Grist)
subgroup: 'length'        // Secondary grouping
// Non-grouped items show in main form
```

### 5. Active vs Hidden
```typescript
active: true   // Rendered in form
active: false  // Skipped entirely
hidden: true   // Rendered but CSS hidden (for validation specs)
```

---

## File Paths in Codebase

### Where to Make Changes
1. **New Components**: `packages/dataset/client/components/`
2. **Type Updates**: Update @operato/dataset OR create wrapper
3. **Media Options**: Already defined in `data-item-type.ts`
4. **Validation**: Already implemented in `media-validation.ts`

### Dependencies Already Available
```json
{
  "@operato/input": "^9.0.0",
  "@operato/dataset": "^9.0.0",
  "lit": "^2.x.x"
}
```

### Key Imports for New Components
```typescript
import { css, html } from 'lit'
import { customElement, property, query } from 'lit/decorators.js'
import { OxFormField } from '@operato/input'
```

---

## Testing Checklist

Before deploying new media components:

- [ ] Component renders with correct icon/label
- [ ] File selection works (click and drag-drop)
- [ ] Value extracted correctly by name attribute
- [ ] Multiple/single file toggle works
- [ ] GraphQL mutation includes files
- [ ] Server-side validation passes
- [ ] Media validation errors displayed to user
- [ ] Attachment created in database
- [ ] File paths stored in DataSample.data
- [ ] Grist grid column type correct for subgroup items
