409 lines
9.4 KiB
TypeScript
409 lines
9.4 KiB
TypeScript
import { createResource, createSignal, For, Show } from 'solid-js';
|
|
import {
|
|
deleteDemoPersonsById,
|
|
type GetDemoPersonsResponse,
|
|
getDemoPersons,
|
|
patchDemoPersonsById,
|
|
postDemoPersons,
|
|
} from './api';
|
|
|
|
type Person = GetDemoPersonsResponse[number];
|
|
|
|
export function PersonCRUD() {
|
|
const [editingId, setEditingId] = createSignal<number | null>(null);
|
|
const [isCreating, setIsCreating] = createSignal(false);
|
|
|
|
// Fetch persons
|
|
const [persons, { refetch }] = createResource<Person[]>(async () => {
|
|
const response = await getDemoPersons();
|
|
return response.data ?? [];
|
|
});
|
|
|
|
// Form state
|
|
const [formData, setFormData] = createSignal({
|
|
first_name: '',
|
|
last_name: '',
|
|
gender: 'other' as 'man' | 'woman' | 'other',
|
|
metadata: {
|
|
login_at: new Date().toISOString(),
|
|
ip: null as string | null,
|
|
agent: null as string | null,
|
|
plan: 'free' as 'free' | 'premium',
|
|
},
|
|
});
|
|
|
|
const resetForm = () => {
|
|
setFormData({
|
|
first_name: '',
|
|
last_name: '',
|
|
gender: 'other',
|
|
metadata: {
|
|
login_at: new Date().toISOString(),
|
|
ip: null,
|
|
agent: null,
|
|
plan: 'free',
|
|
},
|
|
});
|
|
setEditingId(null);
|
|
setIsCreating(false);
|
|
};
|
|
|
|
const handleCreate = async (e: Event) => {
|
|
e.preventDefault();
|
|
try {
|
|
await postDemoPersons({
|
|
body: formData(),
|
|
});
|
|
resetForm();
|
|
refetch();
|
|
} catch (error) {
|
|
console.error('Failed to create person:', error);
|
|
}
|
|
};
|
|
|
|
const handleUpdate = async (e: Event) => {
|
|
e.preventDefault();
|
|
const id = editingId();
|
|
if (id === null) return;
|
|
|
|
try {
|
|
await patchDemoPersonsById({
|
|
path: { id },
|
|
body: formData(),
|
|
});
|
|
resetForm();
|
|
refetch();
|
|
} catch (error) {
|
|
console.error('Failed to update person:', error);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: number) => {
|
|
if (!confirm('Are you sure you want to delete this person?')) return;
|
|
|
|
try {
|
|
await deleteDemoPersonsById({
|
|
path: { id },
|
|
});
|
|
refetch();
|
|
} catch (error) {
|
|
console.error('Failed to delete person:', error);
|
|
}
|
|
};
|
|
|
|
const startEdit = (person: Person) => {
|
|
setFormData({
|
|
first_name: person.first_name,
|
|
last_name: person.last_name ?? '',
|
|
gender: person.gender,
|
|
metadata: person.metadata,
|
|
});
|
|
setEditingId(person.id);
|
|
setIsCreating(false);
|
|
};
|
|
|
|
const startCreate = () => {
|
|
resetForm();
|
|
setIsCreating(true);
|
|
};
|
|
|
|
return (
|
|
<div style={{ padding: '20px', 'max-width': '1200px', margin: '0 auto' }}>
|
|
<h2>Person Management</h2>
|
|
|
|
{/* Create/Edit Form */}
|
|
<Show when={isCreating() || editingId() !== null}>
|
|
<div
|
|
style={{
|
|
border: '1px solid #ccc',
|
|
padding: '20px',
|
|
'margin-bottom': '20px',
|
|
'border-radius': '8px',
|
|
background: '#f9f9f9',
|
|
}}
|
|
>
|
|
<h3>{editingId() !== null ? 'Edit Person' : 'Create Person'}</h3>
|
|
<form onSubmit={editingId() !== null ? handleUpdate : handleCreate}>
|
|
<div style={{ 'margin-bottom': '10px' }}>
|
|
<label style={{ display: 'block', 'margin-bottom': '5px' }}>
|
|
First Name *
|
|
<input
|
|
type="text"
|
|
value={formData().first_name}
|
|
onInput={(e) =>
|
|
setFormData({
|
|
...formData(),
|
|
first_name: e.currentTarget.value,
|
|
})
|
|
}
|
|
required
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px',
|
|
'box-sizing': 'border-box',
|
|
}}
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div style={{ 'margin-bottom': '10px' }}>
|
|
<label style={{ display: 'block', 'margin-bottom': '5px' }}>
|
|
Last Name
|
|
<input
|
|
type="text"
|
|
value={formData().last_name}
|
|
onInput={(e) =>
|
|
setFormData({
|
|
...formData(),
|
|
last_name: e.currentTarget.value,
|
|
})
|
|
}
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px',
|
|
'box-sizing': 'border-box',
|
|
}}
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div style={{ 'margin-bottom': '10px' }}>
|
|
<label style={{ display: 'block', 'margin-bottom': '5px' }}>
|
|
Gender *
|
|
<select
|
|
value={formData().gender}
|
|
onChange={(e) =>
|
|
setFormData({
|
|
...formData(),
|
|
gender: e.currentTarget.value as
|
|
| 'man'
|
|
| 'woman'
|
|
| 'other',
|
|
})
|
|
}
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px',
|
|
'box-sizing': 'border-box',
|
|
}}
|
|
>
|
|
<option value="man">Man</option>
|
|
<option value="woman">Woman</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
<div style={{ 'margin-bottom': '10px' }}>
|
|
<label style={{ display: 'block', 'margin-bottom': '5px' }}>
|
|
Plan
|
|
<select
|
|
value={formData().metadata.plan}
|
|
onChange={(e) =>
|
|
setFormData({
|
|
...formData(),
|
|
metadata: {
|
|
...formData().metadata,
|
|
plan: e.currentTarget.value as 'free' | 'premium',
|
|
},
|
|
})
|
|
}
|
|
style={{
|
|
width: '100%',
|
|
padding: '8px',
|
|
'box-sizing': 'border-box',
|
|
}}
|
|
>
|
|
<option value="free">Free</option>
|
|
<option value="premium">Premium</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', gap: '10px' }}>
|
|
<button
|
|
type="submit"
|
|
style={{ padding: '8px 16px', cursor: 'pointer' }}
|
|
>
|
|
{editingId() !== null ? 'Update' : 'Create'}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={resetForm}
|
|
style={{ padding: '8px 16px', cursor: 'pointer' }}
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</Show>
|
|
|
|
{/* Create Button */}
|
|
<Show when={!isCreating() && editingId() === null}>
|
|
<button
|
|
type="button"
|
|
onClick={startCreate}
|
|
style={{
|
|
padding: '10px 20px',
|
|
'margin-bottom': '20px',
|
|
cursor: 'pointer',
|
|
background: '#4CAF50',
|
|
color: 'white',
|
|
border: 'none',
|
|
'border-radius': '4px',
|
|
}}
|
|
>
|
|
+ Create New Person
|
|
</button>
|
|
</Show>
|
|
|
|
{/* List */}
|
|
<div>
|
|
<Show when={persons.loading}>
|
|
<p>Loading persons...</p>
|
|
</Show>
|
|
|
|
<Show when={persons.error}>
|
|
<p style={{ color: 'red' }}>Error loading persons: {persons.error.message}</p>
|
|
</Show>
|
|
|
|
<Show when={persons()}>
|
|
<table
|
|
style={{
|
|
width: '100%',
|
|
'border-collapse': 'collapse',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
<thead>
|
|
<tr style={{ background: '#f2f2f2' }}>
|
|
<th
|
|
style={{
|
|
padding: '12px',
|
|
'text-align': 'left',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
ID
|
|
</th>
|
|
<th
|
|
style={{
|
|
padding: '12px',
|
|
'text-align': 'left',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
First Name
|
|
</th>
|
|
<th
|
|
style={{
|
|
padding: '12px',
|
|
'text-align': 'left',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
Last Name
|
|
</th>
|
|
<th
|
|
style={{
|
|
padding: '12px',
|
|
'text-align': 'left',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
Gender
|
|
</th>
|
|
<th
|
|
style={{
|
|
padding: '12px',
|
|
'text-align': 'left',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
Plan
|
|
</th>
|
|
<th
|
|
style={{
|
|
padding: '12px',
|
|
'text-align': 'left',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
Created At
|
|
</th>
|
|
<th
|
|
style={{
|
|
padding: '12px',
|
|
'text-align': 'left',
|
|
border: '1px solid #ddd',
|
|
}}
|
|
>
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<For each={persons()}>
|
|
{(person) => (
|
|
<tr>
|
|
<td style={{ padding: '12px', border: '1px solid #ddd' }}>
|
|
{person.id}
|
|
</td>
|
|
<td style={{ padding: '12px', border: '1px solid #ddd' }}>
|
|
{person.first_name}
|
|
</td>
|
|
<td style={{ padding: '12px', border: '1px solid #ddd' }}>
|
|
{person.last_name ?? '-'}
|
|
</td>
|
|
<td style={{ padding: '12px', border: '1px solid #ddd' }}>
|
|
{person.gender}
|
|
</td>
|
|
<td style={{ padding: '12px', border: '1px solid #ddd' }}>
|
|
{person.metadata.plan}
|
|
</td>
|
|
<td style={{ padding: '12px', border: '1px solid #ddd' }}>
|
|
{new Date(person.created_at).toLocaleDateString()}
|
|
</td>
|
|
<td style={{ padding: '12px', border: '1px solid #ddd' }}>
|
|
<button
|
|
type="button"
|
|
onClick={() => startEdit(person)}
|
|
style={{
|
|
padding: '6px 12px',
|
|
'margin-right': '5px',
|
|
cursor: 'pointer',
|
|
background: '#2196F3',
|
|
color: 'white',
|
|
border: 'none',
|
|
'border-radius': '4px',
|
|
}}
|
|
>
|
|
Edit
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleDelete(person.id)}
|
|
style={{
|
|
padding: '6px 12px',
|
|
cursor: 'pointer',
|
|
background: '#f44336',
|
|
color: 'white',
|
|
border: 'none',
|
|
'border-radius': '4px',
|
|
}}
|
|
>
|
|
Delete
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</For>
|
|
</tbody>
|
|
</table>
|
|
</Show>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|