Skip to content

Commit 6e22773

Browse files
committed
feat: refactor DataManager component for improved import/export UI and functionality
1 parent 4d677b5 commit 6e22773

File tree

1 file changed

+73
-110
lines changed

1 file changed

+73
-110
lines changed

src/lib/components/ui/DataManager.svelte

Lines changed: 73 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,14 @@
5151
importSuccess = result.success;
5252
5353
if (result.success) {
54-
// Refresh data after import
55-
setTimeout(() => {
56-
window.location.reload();
57-
}, 2000);
54+
setTimeout(() => window.location.reload(), 2000);
5855
}
5956
} catch (error) {
6057
importMessage = 'Import failed: ' + (error as Error).message;
6158
importSuccess = false;
6259
} finally {
6360
isImporting = false;
64-
if (fileInput) {
65-
fileInput.value = '';
66-
}
61+
if (fileInput) fileInput.value = '';
6762
}
6863
}
6964
</script>
@@ -73,81 +68,41 @@
7368
<div class="export-section">
7469
<h3 class="text-lg font-semibold mb-4 flex items-center" style="color: var(--petalytics-text);">
7570
<Download size={20} class="mr-2" style="color: var(--petalytics-accent);" />
76-
Export Data
71+
export_data
7772
</h3>
7873

7974
<div class="space-y-3">
80-
<!-- Export All -->
81-
<div
82-
class="export-item p-4 rounded-lg border"
83-
style="background: var(--petalytics-surface); border-color: var(--petalytics-border);"
84-
>
85-
<div class="flex items-center justify-between">
86-
<div>
87-
<h4 class="font-medium" style="color: var(--petalytics-text);">Complete Backup</h4>
88-
<p class="text-sm" style="color: var(--petalytics-subtle);">
89-
Export all pets, settings, and journal entries
90-
</p>
91-
</div>
92-
<button
93-
on:click={exportAllData}
94-
disabled={isExporting || pets.length === 0}
95-
class="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors"
96-
>
97-
{#if isExporting}
98-
<div
99-
class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"
100-
></div>
101-
{:else}
102-
<Database size={16} />
103-
{/if}
104-
<span>Export All</span>
105-
</button>
106-
</div>
75+
<!-- Export All (CLI row) -->
76+
<div class="cli-row px-2 py-2">
77+
<span class="label">complete_backup</span>
78+
<span class="value text-xs" style="color: var(--petalytics-subtle);">export all pets, settings, and journal entries</span>
79+
<button type="button" onclick={exportAllData} disabled={isExporting || pets.length === 0} class="arrow-btn ml-2 flex items-center gap-1" aria-label="Export all">
80+
{#if isExporting}
81+
<div class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"></div>
82+
{:else}
83+
<Database size={14} />
84+
{/if}
85+
<span>export</span>
86+
</button>
10787
</div>
10888

109-
<!-- Export Individual Pets -->
11089
{#if pets.length > 0}
11190
<div class="individual-exports">
112-
<h4 class="font-medium mb-2" style="color: var(--petalytics-text);">
113-
Export Individual Pets
114-
</h4>
115-
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
91+
<h4 class="font-medium mb-2" style="color: var(--petalytics-text);">export_individual_pets</h4>
92+
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
11693
{#each pets as pet}
117-
<div
118-
class="pet-export-item p-3 rounded-lg border"
119-
style="background: var(--petalytics-overlay); border-color: var(--petalytics-border);"
120-
>
121-
<div class="flex items-center justify-between">
122-
<div class="flex items-center space-x-2">
123-
<img
124-
src={pet.profileImageUrl || '/images/default-pet.png'}
125-
alt={pet.name}
126-
class="w-8 h-8 rounded-full object-cover"
127-
on:error={(e) => {
128-
const target = e.target as HTMLImageElement;
129-
target.src =
130-
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iMTYiIGZpbGw9IiNGMzRGNEYiLz4KPHN2ZyB4PSI4IiB5PSI4IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9IndoaXRlIj4KICA8cGF0aCBkPSJNNS4yNSA0QzQuNTU5NjQgNCA0IDQuNTU5NjQgNCA1LjI1VjEwLjc1QzQgMTEuNDQwNCA0LjU1OTY0IDEyIDUuMjUgMTJIMTAuNzVDMTEuNDQwNCAxMiAxMiAxMS40NDA0IDEyIDEwLjc1VjUuMjVDMTIgNC41NTk2NCAxMS40NDA0IDQgMTAuNzUgNEg1LjI1WiIvPgo8L3N2Zz4KPC9zdmc+';
131-
}}
132-
/>
133-
<div>
134-
<p class="font-medium text-sm" style="color: var(--petalytics-text);">
135-
{pet.name}
136-
</p>
137-
<p class="text-xs" style="color: var(--petalytics-subtle);">
138-
{pet.journalEntries?.length || 0} entries
139-
</p>
140-
</div>
94+
<div class="cli-row px-2 py-2">
95+
<div class="flex items-center gap-2">
96+
<img src={pet.profileImageUrl || '/images/default-pet.png'} alt={pet.name} class="w-8 h-8 rounded-full object-cover" onerror={(e) => { (e.target as HTMLImageElement).src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iMTYiIGZpbGw9IiNGMzRGNEYiLz4KPHN2ZyB4PSI4IiB5PSI4IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9IndoaXRlIj4KICA8cGF0aCBkPSJNNS4yNSA0QzQuNTU5NjQgNCA0IDQuNTU5NjQgNCA1LjI1VjEwLjc1QzQgMTEuNDQwNCA0LjU1OTY0IDEyIDUuMjUgMTJIMTAuNzVDMTEuNDQwNCAxMiAxMiAxMS40NDA0IDEyIDEwLjc1VjUuMjVDMTIgNC41NTk2NCAxMS40NDA0IDQgMTAuNzUgNEg1LjI1WiIvPgo8L3N2Zz4KPC9zdmc+'; }} />
97+
<div>
98+
<p class="font-medium text-sm" style="color: var(--petalytics-text);">{pet.name}</p>
99+
<p class="text-xs" style="color: var(--petalytics-subtle);">{pet.journalEntries?.length || 0} entries</p>
141100
</div>
142-
<button
143-
on:click={() => exportSinglePet(pet)}
144-
disabled={isExporting}
145-
class="bg-gray-100 hover:bg-gray-200 disabled:bg-gray-50 disabled:cursor-not-allowed text-gray-700 text-xs px-2 py-1 rounded flex items-center space-x-1 transition-colors"
146-
>
147-
<FileText size={12} />
148-
<span>Export</span>
149-
</button>
150101
</div>
102+
<button type="button" onclick={() => exportSinglePet(pet)} disabled={isExporting} class="arrow-btn ml-auto flex items-center gap-1 text-xs" aria-label={`Export ${pet.name}`}>
103+
<FileText size={12} />
104+
<span>export</span>
105+
</button>
151106
</div>
152107
{/each}
153108
</div>
@@ -160,58 +115,30 @@
160115
<div class="import-section">
161116
<h3 class="text-lg font-semibold mb-4 flex items-center" style="color: var(--petalytics-text);">
162117
<Upload size={20} class="mr-2" style="color: var(--petalytics-accent);" />
163-
Import Data
118+
import_data
164119
</h3>
165120

166-
<div
167-
class="import-area p-6 rounded-lg border-2 border-dashed text-center"
168-
style="border-color: var(--petalytics-border);"
169-
>
121+
<div class="import-area p-6 rounded-lg border-2 border-dashed text-center" style="border-color: var(--petalytics-border);">
170122
<Upload size={32} style="color: var(--petalytics-subtle);" class="mx-auto mb-3" />
171-
<p class="mb-3" style="color: var(--petalytics-text);">
172-
Select a JSONL backup file to import
173-
</p>
174-
<input
175-
bind:this={fileInput}
176-
type="file"
177-
accept=".jsonl"
178-
on:change={handleImport}
179-
class="hidden"
180-
/>
181-
<button
182-
on:click={() => fileInput?.click()}
183-
disabled={isImporting}
184-
class="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white px-4 py-2 rounded-lg flex items-center space-x-2 mx-auto transition-colors"
185-
>
123+
<p class="mb-3" style="color: var(--petalytics-text);">Select a JSONL backup file to import</p>
124+
<input bind:this={fileInput} type="file" accept=".jsonl" onchange={handleImport} class="hidden" />
125+
<button type="button" onclick={() => fileInput?.click()} disabled={isImporting} class="arrow-btn mx-auto flex items-center gap-2" aria-label="Choose file to import">
186126
{#if isImporting}
187-
<div
188-
class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"
189-
></div>
190-
<span>Importing...</span>
127+
<div class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"></div>
128+
<span>importing...</span>
191129
{:else}
192130
<Upload size={16} />
193-
<span>Choose File</span>
131+
<span>choose_file</span>
194132
{/if}
195133
</button>
196134

197135
{#if importMessage}
198-
<div
199-
class="mt-4 p-3 rounded"
200-
class:bg-green-100={importSuccess}
201-
class:bg-red-100={!importSuccess}
202-
>
203-
<p
204-
class="text-sm"
205-
class:text-green-800={importSuccess}
206-
class:text-red-800={!importSuccess}
207-
>
208-
{importMessage}
209-
</p>
136+
<div class="mt-4 p-3 rounded" class:bg-green-100={importSuccess} class:bg-red-100={!importSuccess}>
137+
<p class="text-sm" class:text-green-800={importSuccess} class:text-red-800={!importSuccess}>{importMessage}</p>
210138
</div>
211139
{/if}
212140
</div>
213141

214-
<!-- Data Format Info -->
215142
<div class="format-info mt-4 p-3 rounded" style="background: var(--petalytics-overlay);">
216143
<h4 class="font-medium text-sm mb-2" style="color: var(--petalytics-text);">Data Format</h4>
217144
<ul class="text-xs space-y-1" style="color: var(--petalytics-subtle);">
@@ -223,3 +150,39 @@
223150
</div>
224151
</div>
225152
</div>
153+
154+
<style>
155+
.cli-row {
156+
display: flex;
157+
align-items: center;
158+
border: 1px solid transparent;
159+
border-radius: 6px;
160+
transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
161+
background: var(--petalytics-surface);
162+
border-color: var(--petalytics-border);
163+
}
164+
.cli-row:hover {
165+
background: var(--petalytics-highlight-low);
166+
border-color: var(--petalytics-border);
167+
}
168+
.label { color: var(--petalytics-foam); }
169+
.value { margin-left: auto; text-align: right; flex: 1 1 auto; }
170+
.arrow-btn {
171+
font-family: 'JetBrains Mono', monospace;
172+
font-size: 0.85rem;
173+
line-height: 1rem;
174+
background: transparent;
175+
border: 1px solid var(--petalytics-border);
176+
color: var(--petalytics-subtle);
177+
padding: 0.25rem 0.5rem;
178+
border-radius: 4px;
179+
cursor: pointer;
180+
}
181+
.arrow-btn:hover { background: var(--petalytics-highlight-low); color: var(--petalytics-text); }
182+
.arrow-btn:disabled { opacity: 0.5; cursor: not-allowed; }
183+
.arrow-btn:focus-visible {
184+
outline: none;
185+
border-color: var(--petalytics-accent);
186+
box-shadow: 0 0 0 2px color-mix(in oklab, var(--petalytics-accent) 35%, transparent);
187+
}
188+
</style>

0 commit comments

Comments
 (0)