This commit is contained in:
parent
3b51f4aee1
commit
9fba9cc5d7
8 changed files with 657 additions and 7 deletions
54
client/src/lib/api.ts
Normal file
54
client/src/lib/api.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
const BASE_URL = '/';
|
||||||
|
|
||||||
|
export async function fetchItems<T>(resource: 'users' | 'vehicles' | 'jobs'): Promise<T[]> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BASE_URL}${resource}`);
|
||||||
|
if (!response.ok) throw new Error(`Failed to fetch ${resource}`);
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createItem<T>(resource: 'users' | 'vehicles' | 'jobs', data: any): Promise<T> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BASE_URL}${resource}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error(`Failed to create ${resource}`);
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateItem<T>(resource: 'users' | 'vehicles' | 'jobs', id: number, data: any): Promise<T> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BASE_URL}${resource}/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error(`Failed to update ${resource}`);
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteItem(resource: 'users' | 'vehicles' | 'jobs', id: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BASE_URL}${resource}/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error(`Failed to delete ${resource}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
// place files you want to import through the `$lib` alias in this folder.
|
|
||||||
27
client/src/lib/types.ts
Normal file
27
client/src/lib/types.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
export interface Job {
|
||||||
|
id: number;
|
||||||
|
clientName: string;
|
||||||
|
pickup: string;
|
||||||
|
dropoff: string;
|
||||||
|
vehicleId: number;
|
||||||
|
dateCreated: Date;
|
||||||
|
lastUpdated?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
password?: string;
|
||||||
|
displayName: string;
|
||||||
|
dateCreated: Date;
|
||||||
|
lastUpdated?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Vehicle {
|
||||||
|
id: number;
|
||||||
|
make: string;
|
||||||
|
model: string;
|
||||||
|
year: number;
|
||||||
|
dateCreated: Date;
|
||||||
|
lastUpdated?: Date;
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,24 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import favicon from '$lib/assets/favicon.svg';
|
import favicon from '$lib/assets/favicon.svg';
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
const queryClient = new QueryClient();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<link rel="icon" href={favicon} />
|
<link rel="icon" href={favicon} />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{@render children()}
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<main class="container">
|
||||||
|
{@render children()}
|
||||||
|
</main>
|
||||||
|
</QueryClientProvider>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,67 @@
|
||||||
<h1>Welcome to SvelteKit</h1>
|
<main class="container">
|
||||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
<h1 class="title">Test Web</h1>
|
||||||
|
|
||||||
|
<div class="menu-grid">
|
||||||
|
<a href="/jobs" class="card">
|
||||||
|
<h2>Jobs</h2>
|
||||||
|
<p>Manage job details.</p>
|
||||||
|
</a>
|
||||||
|
<a href="/users" class="card">
|
||||||
|
<h2>Users</h2>
|
||||||
|
<p>Manage user details.</p>
|
||||||
|
</a>
|
||||||
|
<a href="/vehicles" class="card">
|
||||||
|
<h2>Vehicles</h2>
|
||||||
|
<p>Manage vehicle details.</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border-color: #2563eb;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
margin: 0;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
163
client/src/routes/jobs/+page.svelte
Normal file
163
client/src/routes/jobs/+page.svelte
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { createQuery } from '@tanstack/svelte-query';
|
||||||
|
import type { Job } from '$lib/types';
|
||||||
|
import { fetchItems, createItem } from '$lib/api';
|
||||||
|
|
||||||
|
const query = createQuery<Job[]>(() => ({
|
||||||
|
queryKey: ['jobs'],
|
||||||
|
queryFn: () => fetchItems("jobs"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let newRow = $state({
|
||||||
|
clientName: "",
|
||||||
|
pickup: "",
|
||||||
|
dropoff: "",
|
||||||
|
vehicleId: 0,
|
||||||
|
} as Job);
|
||||||
|
|
||||||
|
async function addItem(e: SubmitEvent) {
|
||||||
|
await createItem('jobs', newRow);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1 class="title">Job Management</h1>
|
||||||
|
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">Add New Job</h2>
|
||||||
|
<form onsubmit={addItem} class="form">
|
||||||
|
<div class="field">
|
||||||
|
<label for="job_client_name" class="label">Client Name</label>
|
||||||
|
<input id="job_client_name" bind:value={newRow.clientName} required class="input" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="job_pickup" class="label">Pickup</label>
|
||||||
|
<input id="job_pickup" bind:value={newRow.pickup} required class="input" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="job_dropoff" class="label">Dropoff</label>
|
||||||
|
<input id="job_dropoff" bind:value={newRow.dropoff} required class="input" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="job_vehicleId" class="label">Vehicle</label>
|
||||||
|
<input type="numeric" id="job_vehicleId" bind:value={newRow.vehicleId} required class="input" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Add Job</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">Existing Jobs</h2>
|
||||||
|
{#if query.error}
|
||||||
|
<div class="alert error">
|
||||||
|
{query.error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if query.isLoading}
|
||||||
|
<p>Loading...</p>
|
||||||
|
{:else}
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="cell">ID</th>
|
||||||
|
<th class="cell">Make</th>
|
||||||
|
<th class="cell">Model</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each query.data || [] as row}
|
||||||
|
<tr>
|
||||||
|
<td class="cell">{row.clientName}</td>
|
||||||
|
<td class="cell">{row.pickup}</td>
|
||||||
|
<td class="cell">{row.dropoff}</td>
|
||||||
|
<td class="cell">{row.vehicleId}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
color: #991b1b;
|
||||||
|
border-color: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: 0.625rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table thead {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
166
client/src/routes/users/+page.svelte
Normal file
166
client/src/routes/users/+page.svelte
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { createQuery } from '@tanstack/svelte-query';
|
||||||
|
import type { User } from '$lib/types';
|
||||||
|
import { fetchItems, createItem } from '$lib/api';
|
||||||
|
|
||||||
|
const query = createQuery<User[]>(() => ({
|
||||||
|
queryKey: ['users'],
|
||||||
|
queryFn: () => fetchItems("users"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let newRow = $state({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
displayName: ""
|
||||||
|
} as User);
|
||||||
|
|
||||||
|
async function addItem(e: SubmitEvent) {
|
||||||
|
await createItem('users', newRow);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<h1 class="title">User Management</h1>
|
||||||
|
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">Add New User</h2>
|
||||||
|
<form onsubmit={addItem} class="form">
|
||||||
|
<div class="field">
|
||||||
|
<label for="user_username" class="label">Username</label>
|
||||||
|
<input id="user_username" bind:value={newRow.username} required class="input" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="user_password" class="label">Password</label>
|
||||||
|
<input type="password" id="user_password" bind:value={newRow.password} required class="input" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="user_displayName" class="label">Display Name</label>
|
||||||
|
<input id="user_displayName" bind:value={newRow.displayName} required class="input" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Add User</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">Existing Users</h2>
|
||||||
|
{#if query.error}
|
||||||
|
<div class="alert error">
|
||||||
|
{query.error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if query.isLoading}
|
||||||
|
<p>Loading...</p>
|
||||||
|
{:else}
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="cell">ID</th>
|
||||||
|
<th class="cell">Username</th>
|
||||||
|
<th class="cell">Password</th>
|
||||||
|
<th class="cell">DisplayName</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each query.data || [] as row}
|
||||||
|
<tr>
|
||||||
|
<td class="cell">{row.id}</td>
|
||||||
|
<td class="cell">{row.username}</td>
|
||||||
|
<td class="cell">{row.password}</td>
|
||||||
|
<td class="cell">{row.displayName}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
color: #991b1b;
|
||||||
|
border-color: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: 0.625rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table thead {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
163
client/src/routes/vehicles/+page.svelte
Normal file
163
client/src/routes/vehicles/+page.svelte
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { createQuery } from '@tanstack/svelte-query';
|
||||||
|
import type { Vehicle } from '$lib/types';
|
||||||
|
import { fetchItems, createItem } from '$lib/api';
|
||||||
|
|
||||||
|
const query = createQuery<Vehicle[]>(() => ({
|
||||||
|
queryKey: ['vehicles'],
|
||||||
|
queryFn: () => fetchItems("vehicles"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let newRow = $state({
|
||||||
|
make: "",
|
||||||
|
model: "",
|
||||||
|
year: new Date().getFullYear()
|
||||||
|
} as Vehicle);
|
||||||
|
|
||||||
|
async function addItem(e: SubmitEvent) {
|
||||||
|
await createItem('vehicles', newRow);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<h1 class="title">Vehicle Management</h1>
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">Add New Vehicle</h2>
|
||||||
|
<form onsubmit={addItem} class="form">
|
||||||
|
<div class="field">
|
||||||
|
<label for="user_name" class="label">Make</label>
|
||||||
|
<input id="user_name" bind:value={newRow.make} required class="input" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="user_pass" class="label">Model</label>
|
||||||
|
<input type="password" id="user_pass" bind:value={newRow.model} required class="input" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="user_disp" class="label">Year</label>
|
||||||
|
<input id="user_disp" bind:value={newRow.year} required class="input" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Add Vehicle</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">Existing Vehicles</h2>
|
||||||
|
{#if query.error}
|
||||||
|
<div class="alert error">
|
||||||
|
{query.error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if query.isLoading}
|
||||||
|
<p>Loading...</p>
|
||||||
|
{:else}
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="cell">ID</th>
|
||||||
|
<th class="cell">Make</th>
|
||||||
|
<th class="cell">Model</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each query.data || [] as row}
|
||||||
|
<tr>
|
||||||
|
<td class="cell">{row.id}</td>
|
||||||
|
<td class="cell">{row.make}</td>
|
||||||
|
<td class="cell">{row.model}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
color: #991b1b;
|
||||||
|
border-color: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: 0.625rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table thead {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue