feat: export gear maintenance log as CSV
This commit is contained in:
@@ -83,6 +83,41 @@
|
|||||||
|
|
||||||
const GEAR_ICONS: Record<string, string> = { bike: '🚲', shoes: '👟', skis: '🎿', other: '⚙️' };
|
const GEAR_ICONS: Record<string, string> = { bike: '🚲', shoes: '👟', skis: '🎿', other: '⚙️' };
|
||||||
|
|
||||||
|
function exportGearCsv() {
|
||||||
|
const esc = (v: unknown) => `"${String(v ?? '').replace(/"/g, '""')}"`;
|
||||||
|
const rows: string[] = [
|
||||||
|
['gear_name', 'gear_type', 'gear_distance_km', 'part_name', 'part_threshold_km',
|
||||||
|
'part_distance_since_last_km', 'replacement_date', 'replacement_note'].join(',')
|
||||||
|
];
|
||||||
|
for (const item of gearItems) {
|
||||||
|
const gearKm = ((gearDistances[item.name] ?? 0) / 1000).toFixed(1);
|
||||||
|
const parts = item.parts ?? [];
|
||||||
|
if (parts.length === 0) {
|
||||||
|
rows.push([esc(item.name), esc(item.type), gearKm, '', '', '', '', ''].join(','));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const part of parts) {
|
||||||
|
const partKm = partDistanceKm(item.name, part).toFixed(1);
|
||||||
|
const reps = part.replacements.slice().reverse();
|
||||||
|
if (reps.length === 0) {
|
||||||
|
rows.push([esc(item.name), esc(item.type), gearKm, esc(part.name),
|
||||||
|
part.threshold_km ?? '', partKm, '', ''].join(','));
|
||||||
|
} else {
|
||||||
|
for (const rep of reps) {
|
||||||
|
rows.push([esc(item.name), esc(item.type), gearKm, esc(part.name),
|
||||||
|
part.threshold_km ?? '', partKm, rep.date, esc(rep.note ?? '')].join(','));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const blob = new Blob([rows.join('\n')], { type: 'text/csv' });
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = 'gear.csv';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
}
|
||||||
|
|
||||||
function fmtDate(iso: string): string {
|
function fmtDate(iso: string): string {
|
||||||
const [y, m, d] = iso.split('-');
|
const [y, m, d] = iso.split('-');
|
||||||
return `${d}/${m}/${y}`;
|
return `${d}/${m}/${y}`;
|
||||||
@@ -847,6 +882,12 @@
|
|||||||
disabled={gearAdding || !gearAddName.trim()}
|
disabled={gearAdding || !gearAddName.trim()}
|
||||||
class="text-xs px-3 py-1.5 rounded-lg border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white transition-colors disabled:opacity-40"
|
class="text-xs px-3 py-1.5 rounded-lg border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white transition-colors disabled:opacity-40"
|
||||||
>{gearAdding ? 'Adding…' : '+ Add gear'}</button>
|
>{gearAdding ? 'Adding…' : '+ Add gear'}</button>
|
||||||
|
{#if gearItems.length > 0}
|
||||||
|
<button
|
||||||
|
on:click={exportGearCsv}
|
||||||
|
class="text-xs px-3 py-1.5 rounded-lg border border-zinc-700 hover:border-zinc-500 text-zinc-400 hover:text-white transition-colors ml-auto"
|
||||||
|
>Export CSV</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user