Add admin pet detail pages: profile, photos, medical, fosters (Task 32b)
Pet layout: tabbed nav with counts for photos/medical/fosters, status badge in header. Detail overview: summary cards and compatibility grid. Profile: full edit form including adoption info conditional on status, audit logged. Photos: upload, set primary (star toggle), delete, reorder with prev/next arrows. Medical: add/delete records grouped by date, cost stored as cents. Fosters: assign by user account (linked to foster-role users), set current, delete, audit logged throughout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { fosterAssignments, users } from '$lib/server/schema';
|
||||
import { eq, and, asc } from 'drizzle-orm';
|
||||
import { error, fail } from '@sveltejs/kit';
|
||||
import { logAudit } from '$lib/server/audit';
|
||||
import { hasPermission, canViewPets } from '$lib/roles';
|
||||
|
||||
export const load: PageServerLoad = async ({ parent, locals }) => {
|
||||
if (!canViewPets(locals.user!.role)) error(403, 'Access denied');
|
||||
const { pet } = await parent();
|
||||
|
||||
const assignments = await db
|
||||
.select({
|
||||
id: fosterAssignments.id,
|
||||
petId: fosterAssignments.petId,
|
||||
userId: fosterAssignments.userId,
|
||||
startDate: fosterAssignments.startDate,
|
||||
endDate: fosterAssignments.endDate,
|
||||
isCurrent: fosterAssignments.isCurrent,
|
||||
notes: fosterAssignments.notes,
|
||||
createdAt: fosterAssignments.createdAt,
|
||||
fosterName: users.name,
|
||||
fosterEmail: users.email
|
||||
})
|
||||
.from(fosterAssignments)
|
||||
.leftJoin(users, eq(fosterAssignments.userId, users.id))
|
||||
.where(eq(fosterAssignments.petId, pet.id))
|
||||
.orderBy(asc(fosterAssignments.startDate));
|
||||
|
||||
// Load active foster-role users for the dropdown
|
||||
const fosterUsers = await db
|
||||
.select({ id: users.id, name: users.name, email: users.email })
|
||||
.from(users)
|
||||
.where(and(eq(users.role, 'foster'), eq(users.active, true)))
|
||||
.orderBy(asc(users.name));
|
||||
|
||||
return { assignments, fosterUsers };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
add: async ({ request, params, locals }) => {
|
||||
if (!hasPermission(locals.user!.role, 'pets')) error(403, 'Access denied');
|
||||
const petId = parseInt(params.id, 10);
|
||||
const fd = await request.formData();
|
||||
|
||||
const userIdStr = fd.get('userId')?.toString();
|
||||
const userId = userIdStr ? parseInt(userIdStr, 10) : null;
|
||||
|
||||
if (!userId || isNaN(userId)) {
|
||||
return fail(400, { error: 'Foster user is required' });
|
||||
}
|
||||
|
||||
const startDateStr = fd.get('startDate')?.toString() || null;
|
||||
const startDate = startDateStr ? new Date(startDateStr) : new Date();
|
||||
const endDateStr = fd.get('endDate')?.toString() || null;
|
||||
const endDate = endDateStr ? new Date(endDateStr) : null;
|
||||
const notes = fd.get('notes')?.toString().trim() || null;
|
||||
const isCurrent = fd.get('isCurrent') === 'on';
|
||||
|
||||
// If marking as current, unset all others
|
||||
if (isCurrent) {
|
||||
await db.update(fosterAssignments).set({ isCurrent: false }).where(eq(fosterAssignments.petId, petId));
|
||||
}
|
||||
|
||||
const [inserted] = await db.insert(fosterAssignments).values({
|
||||
petId,
|
||||
userId,
|
||||
startDate,
|
||||
endDate,
|
||||
notes,
|
||||
isCurrent
|
||||
}).$returningId();
|
||||
|
||||
await logAudit({
|
||||
userId: locals.user!.id,
|
||||
action: 'create',
|
||||
entity: 'foster_assignment',
|
||||
entityId: inserted.id,
|
||||
diff: { petId, userId, isCurrent }
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
setCurrent: async ({ request, params, locals }) => {
|
||||
if (!hasPermission(locals.user!.role, 'pets')) error(403, 'Access denied');
|
||||
const petId = parseInt(params.id, 10);
|
||||
const fd = await request.formData();
|
||||
const assignmentId = parseInt(fd.get('assignmentId') as string, 10);
|
||||
|
||||
if (!assignmentId || isNaN(assignmentId)) {
|
||||
return fail(400, { error: 'Invalid assignment ID' });
|
||||
}
|
||||
|
||||
const isCurrent = fd.get('isCurrent') === 'on';
|
||||
|
||||
if (isCurrent) {
|
||||
await db.update(fosterAssignments).set({ isCurrent: false }).where(eq(fosterAssignments.petId, petId));
|
||||
}
|
||||
|
||||
await db.update(fosterAssignments).set({ isCurrent }).where(eq(fosterAssignments.id, assignmentId));
|
||||
|
||||
await logAudit({
|
||||
userId: locals.user!.id,
|
||||
action: 'update',
|
||||
entity: 'foster_assignment',
|
||||
entityId: assignmentId,
|
||||
diff: { petId, isCurrent }
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
delete: async ({ request, params, locals }) => {
|
||||
if (!hasPermission(locals.user!.role, 'pets')) error(403, 'Access denied');
|
||||
const petId = parseInt(params.id, 10);
|
||||
const fd = await request.formData();
|
||||
const assignmentId = parseInt(fd.get('assignmentId') as string, 10);
|
||||
|
||||
if (!assignmentId || isNaN(assignmentId)) {
|
||||
return fail(400, { error: 'Invalid assignment ID' });
|
||||
}
|
||||
|
||||
await db.delete(fosterAssignments).where(eq(fosterAssignments.id, assignmentId));
|
||||
|
||||
await logAudit({
|
||||
userId: locals.user!.id,
|
||||
action: 'delete',
|
||||
entity: 'foster_assignment',
|
||||
entityId: assignmentId,
|
||||
diff: { petId }
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user