clqms-fe1/COMPONENT_ORGANIZATION.md
mahdahar ae806911be feat(equipment,organization): add equipment API client and complete organization module structure
- Add equipment.js API client with full CRUD operations
- Add organization sub-routes: account, department, discipline, instrument, site, workstation
- Create EquipmentModal and DeleteConfirmModal components
- Update master-data navigation and sidebar
- Update tests, containers, counters, geography, locations, occupations, specialties, testmap, and valuesets pages
- Add COMPONENT_ORGANIZATION.md documentation
2026-02-24 16:53:04 +07:00

4.4 KiB

Component Organization Guide

Guide for splitting large components and modals into manageable files.

When to Split Components

Split a component when:

  • File exceeds 200 lines
  • Component has multiple distinct sections (tabs, steps, panels)
  • Logic becomes hard to follow
  • Multiple developers work on different parts

Modal Organization Pattern

Structure for Large Modals

src/routes/(app)/feature/
├── +page.svelte              # Main page
├── FeatureModal.svelte       # Main modal container
└── feature-modal/            # Modal sub-components (kebab-case folder)
    ├── modals/               # Nested modals
    │   └── PickerModal.svelte
    └── tabs/                 # Tab content components
        ├── BasicInfoTab.svelte
        ├── SettingsTab.svelte
        └── AdvancedTab.svelte

Example: Test Form Modal

Location: src/routes/(app)/master-data/tests/test-modal/

<!-- TestFormModal.svelte -->
<script>
  import Modal from '$lib/components/Modal.svelte';
  import BasicInfoTab from './test-modal/tabs/BasicInfoTab.svelte';
  import TechDetailsTab from './test-modal/tabs/TechDetailsTab.svelte';
  import CalcDetailsTab from './test-modal/tabs/CalcDetailsTab.svelte';
  
  let { open = $bindable(false), test = null } = $props();
  let activeTab = $state('basic');
  let formData = $state({});
</script>

<Modal bind:open title={test ? 'Edit Test' : 'New Test'} size="xl">
  {#snippet children()}
    <div class="tabs tabs-boxed mb-4">
      <button class="tab" class:tab-active={activeTab === 'basic'} onclick={() => activeTab = 'basic'}>Basic</button>
      <button class="tab" class:tab-active={activeTab === 'technical'} onclick={() => activeTab = 'technical'}>Technical</button>
      <button class="tab" class:tab-active={activeTab === 'calculation'} onclick={() => activeTab = 'calculation'}>Calculation</button>
    </div>
    
    {#if activeTab === 'basic'}
      <BasicInfoTab bind:formData />
    {:else if activeTab === 'technical'}
      <TechDetailsTab bind:formData />
    {:else if activeTab === 'calculation'}
      <CalcDetailsTab bind:formData />
    {/if}
  {/snippet}
</Modal>
<!-- test-modal/tabs/BasicInfoTab.svelte -->
<script>
  let { formData = $bindable({}) } = $props();
</script>

<div class="space-y-4">
  <div class="form-control">
    <label class="label">Test Name</label>
    <input class="input input-bordered" bind:value={formData.name} />
  </div>
  <div class="form-control">
    <label class="label">Description</label>
    <textarea class="textarea textarea-bordered" bind:value={formData.description}></textarea>
  </div>
</div>

Data Flow

Parent to Child

  • Pass data via props (bind:formData)
  • Use $bindable() for two-way binding
  • Keep state in parent when shared across tabs

Child to Parent

  • Use callbacks for actions (onSave, onClose)
  • Modify bound data directly (with $bindable)
  • Emit events for complex interactions

Props Interface Pattern

// Define props with JSDoc
/** @type {{ formData: Object, onValidate: Function, readonly: boolean }} */
let { 
  formData = $bindable({}), 
  onValidate = () => true,
  readonly = false 
} = $props();

Naming Conventions

  • Main modal: {Feature}Modal.svelte (e.g., TestFormModal.svelte)
  • Tab components: {TabName}Tab.svelte (e.g., BasicInfoTab.svelte)
  • Nested modals: {Action}Modal.svelte (e.g., ConfirmDeleteModal.svelte)
  • Folder names: kebab-case matching the modal name (e.g., test-modal/)

Shared State Management

For complex modals with shared state across tabs:

// In main modal
let sharedState = $state({
  dirty: false,
  errors: {},
  selectedItems: []
});

// Pass to tabs
<TabName formData={formData} sharedState={sharedState} />

Import Order in Sub-components

Same as main components:

  1. Svelte imports
  2. $lib/* imports
  3. External libraries
  4. Relative imports (other tabs/modals)

Testing Split Components

# Test individual tab component
vitest run src/routes/feature/modal-tabs/BasicInfoTab.test.js

# Test main modal integration
vitest run src/routes/feature/FeatureModal.test.js

Benefits

  • Maintainability: Each file has single responsibility
  • Collaboration: Multiple developers can work on different tabs
  • Testing: Test individual sections in isolation
  • Performance: Only render visible tab content
  • Reusability: Tabs can be used in different modals