โ๏ธ Datapoint Editor Components
Inline editing and CSV import components for financial datapoints. Located in lib/components/ui/data-editor/.
graph TD
DIM["DataImportModal<br/><small>CSV file import modal</small>"] --> MB["ModalBase"]
DIM --> CSE["CsvEditor<br/><small>CSV preview/edit table</small>"]
DE["DataEditor<br/><small>Inline tabular editor</small>"] --> SDP["SingleDatePicker"]
DIM -.->|used by| FXI["FX CSV Import"]
DE -.->|used by| FXD["FX Data Editor"]
style DIM fill:#e8f5e9,stroke:#2e7d32
style CSE fill:#e8f5e9,stroke:#2e7d32
style DE fill:#e8f5e9,stroke:#2e7d32
style MB fill:#f3e5f5,stroke:#7b1fa2
style SDP fill:#e3f2fd,stroke:#1565c0
โ๏ธ DataEditor
An inline tabular editor for structured data (add, edit, delete rows).
- Editable cells with type-aware inputs (text, number, date)
- Add row button with empty row template
- Delete row: the row is only marked deleted, and its Undo action restores a deleted or edited row and removes a new one; selected rows can be deleted together. Nothing is written until the owner saves
- Validation per cell with error highlighting
- Toolbar counters: N modified, N deleted, N new
- Rows carry a
staleDayscount (backward-filled points); when any exist, a switch with aโ ๏ธ Ncounter hides them (dataEditor.staleTooltip) readonlyrows (with areadonlyReasontooltip) can be deleted but not edited- Optional numeric columns flagged
erasablerenderErasableNumberCell: its eraser (orDeletein an empty input) sets the sentinel-1without a confirmation, since the row's Undo covers a slip; the backend price upsert turns-1intoNULL - Import CSV merges each parsed line into the row with the same date and the same values for
the
importMatchKeyscolumns (prop, default[]: the date alone, as for prices and FX rates; the asset events editor passes['type']). Among several matches an editable row wins over a read-only one, and a line whose every match is read-only is skipped; only non-empty values are merged. An unmatched line is appended under a uniquerowId, the date thendate#2,date#3โฆ, so two new rows can share a date scrollToDate(date)scrolls to a row
Used by: the FX detail page (fx/FxDataEditorSection.svelte) and the asset detail page
(assets/AssetDataEditorSection.svelte), below.
๐พ Saving: the FX and asset wrappers
DataEditor never writes: its wrapper collects the dirty rows and saves them on Save.
FX (FxDataEditorSection) |
Asset (AssetDataEditorSection) |
|
|---|---|---|
| Tables and columns | one table: rate |
Prices: close (required), open, high, low, volume (erasable). Events: type (one of the five event types) and amount (required), notes |
| Written through | POST /api/v1/fx/currencies/rate; a pair shown inverted (its base after its quote in alphabetical order) stores 1 / rate under the alphabetical pair |
POST /api/v1/assets/prices; POST /api/v1/assets/events, a saved event with its id (edited in place, type included), a new one without |
| Deleted through | DELETE /api/v1/fx/currencies/rate |
DELETE /api/v1/assets/prices; DELETE /api/v1/assets/events by id, whose per-item in_use result (an event a transaction uses) is reported, not deleted. An event row never saved sends no DELETE |
| Rows not sent | A rate that is not a number above 0, counted as skipped (invalid). When every row is invalid and none is deleted, the save stops with Rate must be strictly greater than zero (0 is not allowed). | A price whose close is not a number above 0, counted as skipped (invalid); an event without a type or a numeric amount |
Both wrappers:
- merge consecutive deleted dates into ranges;
- draw the pending values as a purple preview line (
__preview__,#a855f7) until saved or cancelled; the FX one draws only rates above 0; - after a save that appended rows outside the loaded range, hand the page the widened range
through
onsave, and the page moves its period there; - expose
scrollToDate(date): with the editor open, a double-click on the chart (a long press of about 0.8 s on touch) scrolls to that date.
Asset only:
- Two tabs, Prices and Events, whose badges count the dirty rows, share one Save (n) / Cancel bar. The chart jump picks the Events tab when the date has an event marker, the Prices tab otherwise.
- No currency column. The backend rejects a price or event in a currency other than the asset's (HTTP 400); the editor sends no currency with prices and the asset's with events, and a label beside the tabs reads Prices in USD / Events in USD.
- Auto events (
is_auto) arereadonly: a provider rewrites them at its next sync (see Asset Events). - Saved events travel with their id. A saved event row's
rowIdis its DB id, read back bydbEventId(row)(nullfor a row never saved, whoserowIdis a date:2026-03-15,2026-03-15#2). The upsert sends thatid, so the server edits the row in place, its type included: changing the Type of a saved event no longer adds an event and leaves the old one. A row deleted before its first save sends no DELETE. The eventsDataEditorgetsimportMatchKeys={['type']}: an imported line merges into the row of the same date and type, under the Import CSV rules above, and a line of another type on that date is added. - A refused save (HTTP 400, such as
EVENT_KEY_CONFLICT) shows Save failed: โฆ and does not callonsave: the editor stays open with the pending rows. Prices are sent before events, so the prices of that save may already be stored. - Refresh while editing: a
chartDataoreventsrefresh that lands while a tab has dirty rows is held back until it is clean, so pending edits are never discarded. The FX wrapper rebuilds its rows on every refresh.
The asset import modals wrap DataImportModal, each with a format banner and a ๐ button that
opens the user page (user/assets/detail/data-editor/ and user/assets/detail/events/, with the
locale prefix):
PriceDataImportModalโ headerdate;currency;close, optionalopen;high;low;volume.currencyis a required column for the parser but is not sent: the save uses the asset's.EventDataImportModalโ headerdate;currency;type;amount;notes, withcurrencyandnotesoptional.amountalso reads avaluecolumn (an alias), the name the events export (GET /api/v1/backup/asset/{id}/events?format=csv) gives it, so an exported events file imports back with its other columns ignored. Itssourcecolumn is not read (every row becomes a manual event), and rows sharing a date are still duplicates.
๐ CsvEditor
A CSV preview and editor with column detection and per-row validation.
- Parses CSV content and displays as table
- Detects the separator,
;or,, from the first non-empty line (a line starting withdate;ordate,decides at once, otherwise the first of the two to appear), and reads quoted fields RFC 4180-style. No other separator is recognised - Requires a header on the first non-empty line and matches it by column name,
case-insensitively and in any order: extra columns are ignored, missing required ones produce
one consolidated error. A column may list
aliases(CsvColumnDef.aliases), tried after its label: with both present, the label wins. A tokenA<Breads asB>A, so an FX header can name its direction either way - The identity column is
date(YYYY-MM-DD) unless the caller passes another, such as a distribution'sname - Numbers (
parseNumber):_is dropped as a thousands separator; with both.and,, the last one is the decimal separator and the other is dropped; a lone,is the decimal separator (a second one makes the value invalid). What remains must be a plain number (sign, digits, optional decimals and exponent), so an inner space or a letter is invalid. A column may also pass its ownparseandvalidate - Highlights rows with errors (red) or warnings (yellow), such as a duplicate date
- Editable cells for manual correction
Its messages, one per invalid line:
| Where | Message |
|---|---|
| Header | Missing required columns: <labels>, or Expected header: <identity>;<labelsโฆ> when the first line has no identity column; every data row then reads Fix header first |
| Row | Invalid quoted CSV field |
| Row | Invalid date format: "<value>". Use YYYY-MM-DD, Invalid date: "<value>", or Invalid <identity>: "<value>" for another identity column |
| Row | Required column "<label>" is empty |
| Row | Invalid number in "<label>": "<value>", or Invalid value in "<label>": "<value>" when a column's parse rejects it |
| Row | The column's own validate message, such as Weight must be between 0 and 100 |
| Duplicates | Duplicate <identity>: <value> on every row sharing that value; none of them is imported |
Used by: DataImportModal (FX CSV import preview and the asset price, event and distribution
imports).
๐ฅ DataImportModal
A modal for importing data from CSV files. Extends ModalBase.
- Drag & drop file upload zone
- Direction bar for FX pair direction (with swap button)
- Uses
CsvEditorfor preview - Validation summary before import: N valid row(s), the error count, and duplicates
- Import (N) imports the valid rows only, by default: rows with errors and every duplicated
row stay out. A
strictcaller (the distribution import) allows the import only with no error and no duplicate, and avalidateRowscheck on the whole set (the distribution total) blocks it with its message - Closing after typing, pasting or dropping data asks to confirm (Discard import?)
Used by: FX detail page โ "Import CSV" action, and the asset wrappers PriceDataImportModal,
EventDataImportModal and DistributionDataImportModal. See
FX CSV Import and
Asset Data Editor for user documentation.