Skip to content

feat: import CSV into active codeplug #58

Description

@pskillen

Problem

Import today only creates a new project from the Home page (importNewProject). Operators working on an active codeplug cannot add or refresh data from additional CSV files — e.g. import Channels.csv first, then realise they also need Zones.csv, Contacts.csv, or TG_Lists.csv.

The store already exposes applyImportToActive and applyImportToCodeplug (src/state/codeplugStore.tsx), but no UI calls them. Import docs mention merging into the active project, yet the dropzone is Home-only.

Current merge semantics are also too blunt for incremental import:

  • Importing a file type replaces the entire entity array for that type (channels = result.channels ?? codeplug.channels).
  • Re-imported channels always get new internal ids (parseChannels calls newId()), so zone memberships break unless channel names still resolve.
  • Zone re-resolution by name works when channels are re-imported with the same names (see codeplugStore.test.tsx), but ids churn and duplicates are not handled.

Intended outcome

Allow importing OpenGD77 CSV file(s) into the active codeplug with sensible merge behaviour and operator feedback.

UI

  • Add an Import into active project entry point when a project is open (candidates: Summary, Export, or a sidebar/footer action near Settings).
  • Reuse ImportDropzone + importFiles; wire onResult to applyImportToActive.
  • Show import summary (recognised / skipped / errors) inline after merge.
  • Optional but recommended: preview/confirm step listing what will be added, updated, or replaced before applying.

Merge semantics (design + implement)

Entity matching rules live at the import boundary — adapters know vendor keying; the store applies the merge.

Entity Vendor match key Proposed merge behaviour
Channel Channel Name (case-sensitive) Upsert by name: update existing channel in place (preserve internal id); append channels with new names; do not wipe unrelated channels
Zone Zone Name Upsert by name; re-resolve memberChannelIds from sourceMemberNames
Contact Contact Name Upsert by name
Talk group Contact Name where ID Type=Group Upsert by name
RX group list TG List Name Upsert by name

When only one file type is imported (e.g. Zones.csv alone), other entity arrays are left unchanged (already true today).

Zone member resolution: after channel upsert, rebuild nameToId and re-resolve all zones' memberChannelIds. Surface unresolved member names in the import summary (existing validation/report patterns).

Replace-all escape hatch: optional advanced mode to replace an entire entity type from import (current behaviour) — not required for v1 if upsert covers the main use case.

Example workflow

  1. Operator imports Channels.csv on Home → new project (unchanged).
  2. Later, on Summary/Export, imports Zones.csv into the active project → zones added; members linked to existing channels by name.
  3. Re-imports an updated Channels.csv → channels with matching names updated in place; new names appended; zones stay linked.

Affected

  • UI route with ImportDropzone wired to applyImportToActive
  • applyImportToCodeplug in src/state/codeplugStore.tsx — upsert merge logic per entity type
  • src/lib/import/opengd77/parse.ts — may need merge helpers or pass-through of existing id map
  • src/lib/codeplug.tsbuildNameToChannelId, resolveZoneMembers
  • Tests in src/state/codeplugStore.test.tsx — upsert, partial import, unresolved members
  • docs/features/import/README.md — document active-project import + merge rules

Notes / dependencies

  • OpenGD77 FK rule: channel names are case-sensitive foreign keys across CSV files — merge must preserve exact names.
  • Distinct from Home "new project" import — both flows should coexist.
  • Related: refactor: drop channel number from internal model (assign at export) #53 (drop channel.number from model) — import merge should not depend on channel number as a key.
  • Privacy: operator CSVs stay browser-local.

Out of scope

  • Importing into a non-active project from the project list (pick target project).
  • Three-way conflict UI for field-level diffs (upsert overwrites with imported values is sufficient for v1).
  • Non-OpenGD77 adapters.

Workflow note

Branch from origin/main, atomic conventional commits (store merge logic, then UI, then docs/tests), PR linking Closes #58.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions