Files
frontend/src/lib/components/AdminTable.svelte
Edwin Eames 7ac2c7c59e feat: add admin panel, stats API, and url support across frontend
- Add adminApi with full CRUD for users, companies, listings, oil-prices
- Add statsApi for fetching latest market price aggregates
- Add AdminTable component and /admin page for site management
- Add StatsPrice, UpdateUserRequest, UpdateOilPriceRequest types
- Add url field support in listing create/edit forms
- Update state SVG data for all 6 New England states
- Update county page to display richer listing info (phone, url, bio%)
- Misc layout and style updates across vendor and public routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:34:31 -05:00

101 lines
3.8 KiB
Svelte
Executable File

<script lang="ts">
export let data: any[] = [];
export let columns: { key: string; label: string; type?: 'text' | 'number' | 'boolean' | 'date'; editable?: boolean }[] = [];
export let onSave: (item: any) => Promise<void>;
export let onDelete: (item: any) => Promise<void>;
let editingId: number | null = null;
let editValues: any = {};
let isSaving = false;
let isDeleting = false;
function startEdit(item: any) {
editingId = item.id;
editValues = { ...item };
}
async function save() {
if (!editingId) return;
isSaving = true;
try {
await onSave(editValues);
editingId = null;
editValues = {};
} catch (e) {
alert('Failed to save: ' + e);
} finally {
isSaving = false;
}
}
function cancel() {
editingId = null;
editValues = {};
}
async function handleDelete(item: any) {
if (confirm('Are you sure you want to delete this item?')) {
isDeleting = true;
try {
await onDelete(item);
} catch (e) {
alert('Failed to delete: ' + e);
} finally {
isDeleting = false;
}
}
}
</script>
<div class="overflow-x-auto">
<table class="table table-xs md:table-sm table-pin-rows">
<thead>
<tr>
{#each columns as col}
<th>{col.label}</th>
{/each}
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each data as item (item.id)}
{@const isEditing = editingId === item.id}
<tr class="hover">
{#each columns as col}
<td>
{#if isEditing && col.editable}
{#if col.type === 'boolean'}
<input type="checkbox" class="checkbox checkbox-sm" bind:checked={editValues[col.key]} />
{:else if col.type === 'number'}
<input type="number" class="input input-bordered input-sm w-full" bind:value={editValues[col.key]} />
{:else}
<input type="text" class="input input-bordered input-sm w-full" bind:value={editValues[col.key]} />
{/if}
{:else}
{#if col.type === 'boolean'}
<input type="checkbox" class="checkbox checkbox-xs" checked={item[col.key]} disabled />
{:else}
<div class="truncate max-w-[200px]" title={item[col.key]}>
{item[col.key] ?? ''}
</div>
{/if}
{/if}
</td>
{/each}
<td class="flex gap-2">
{#if isEditing}
<button class="btn btn-xs btn-success" on:click={save} disabled={isSaving}>
{#if isSaving}Saving...{:else}Save{/if}
</button>
<button class="btn btn-xs btn-ghost" on:click={cancel} disabled={isSaving}>Cancel</button>
{:else}
<button class="btn btn-xs btn-primary" on:click={() => startEdit(item)}>Edit</button>
<button class="btn btn-xs btn-error" on:click={() => handleDelete(item)} disabled={isDeleting}>Delete</button>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>