-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
admin page work: facility creation/deletion
- Loading branch information
Showing
8 changed files
with
365 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<div | ||
class="flex flex-col h-full overflow-auto w-full bg-background shadow-lg space-y-4 p-8 pt-6 rounded-md"> | ||
<slot /> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import type { PageServerLoad } from "./$types"; | ||
import prisma from "$lib/prisma"; | ||
|
||
export const load: PageServerLoad = async () => { | ||
return { | ||
totalUsers: await prisma.user.count(), | ||
totalFacilities: await prisma.facility.count(), | ||
divisionStaffAuthorizations: await prisma.userFacilityAssignment.count({ | ||
where: { | ||
assignmentType: "DivisionalStaff" | ||
} | ||
}), | ||
siteAdministrators: await prisma.user.count({ | ||
where: { | ||
isSiteAdmin: true | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<script lang="ts"> | ||
import * as Card from "$lib/components/ui/card"; | ||
import * as Table from "$lib/components/ui/table"; | ||
import {SquareUserIcon, TowerControlIcon, UserIcon} from "lucide-svelte"; | ||
import type {PageData} from "./$types"; | ||
export let data: PageData; | ||
</script> | ||
|
||
<div class="flex items-center justify-between space-y-2"> | ||
<h2 class="text-3xl font-bold tracking-tight">Site Administration</h2> | ||
</div> | ||
|
||
<div class="grid-cols-4 grid gap-4"> | ||
<Card.Root> | ||
<Card.Header | ||
class="flex flex-row items-center justify-between space-y-0 pb-2" | ||
> | ||
<Card.Title class="text-sm font-medium">Users</Card.Title> | ||
<UserIcon class="h-4 w-4 text-muted-foreground" /> | ||
</Card.Header> | ||
<Card.Content> | ||
<div class="text-2xl font-bold">{data.totalUsers}</div> | ||
<a href="/admin/users" class="text-sm text-foreground/80 underline underline-offset-4">Manage Users →</a> | ||
</Card.Content> | ||
</Card.Root> | ||
<Card.Root> | ||
<Card.Header | ||
class="flex flex-row items-center justify-between space-y-0 pb-2" | ||
> | ||
<Card.Title class="text-sm font-medium">Facilities</Card.Title> | ||
<TowerControlIcon class="h-4 w-4 text-muted-foreground" /> | ||
</Card.Header> | ||
<Card.Content> | ||
<div class="text-2xl font-bold">{data.totalFacilities}</div> | ||
<a href="/admin/facilities" class="text-sm text-foreground/80 underline underline-offset-4">Manage Facilities →</a> | ||
</Card.Content> | ||
</Card.Root> | ||
<Card.Root> | ||
<Card.Header | ||
class="flex flex-row items-center justify-between space-y-0 pb-2" | ||
> | ||
<Card.Title class="text-sm font-medium">Staff Authorizations</Card.Title> | ||
<SquareUserIcon class="h-4 w-4 text-muted-foreground" /> | ||
</Card.Header> | ||
<Card.Content> | ||
<div class="text-2xl font-bold">{data.divisionStaffAuthorizations}</div> | ||
<a href="/admin/division_staff" class="text-sm text-foreground/80 underline underline-offset-4">Manage Staff Authorizations →</a> | ||
</Card.Content> | ||
</Card.Root> | ||
<Card.Root> | ||
<Card.Header | ||
class="flex flex-row items-center justify-between space-y-0 pb-2" | ||
> | ||
<Card.Title class="text-sm font-medium">Site Administrators</Card.Title> | ||
<UserIcon class="h-4 w-4 text-muted-foreground" /> | ||
</Card.Header> | ||
<Card.Content> | ||
<div class="text-2xl font-bold">{data.siteAdministrators}</div> | ||
<a href="/admin/admins" class="text-sm text-foreground/80 underline underline-offset-4">Manage Site Administrators →</a> | ||
</Card.Content> | ||
</Card.Root> | ||
</div> | ||
|
||
<div class="grid-cols-3 grid gap-4"> | ||
|
||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import type { PageServerLoad, Actions } from "./$types"; | ||
import prisma from "$lib/prisma"; | ||
import {superValidate} from "sveltekit-superforms/server"; | ||
import {formSchema} from "./schema"; | ||
import {fail} from "@sveltejs/kit"; | ||
import {loadUserData} from "$lib/auth"; | ||
|
||
export const load: PageServerLoad = async ({ parent }) => { | ||
let { user } = await parent(); | ||
if (!user.isSiteAdmin) { return {}; } | ||
|
||
return { | ||
facilities: await prisma.facility.findMany()!, | ||
form: await superValidate(formSchema) | ||
} | ||
} | ||
|
||
export const actions: Actions = { | ||
create: async (event) => { | ||
let form = await superValidate(event, formSchema); | ||
|
||
if (!form.valid) { | ||
return fail(400, { form }); | ||
} | ||
|
||
let { user } = await loadUserData(event.cookies, null); | ||
if (!user.isSiteAdmin) { | ||
return fail(400, { form }); | ||
} | ||
|
||
await prisma.facility.create({ | ||
data: { | ||
id: form.data.id.toString(), | ||
name: form.data.name.toString(), | ||
dotnetId: form.data.dotnetId.toString(), | ||
dotnetType: form.data.dotnetType.toString() === 'Division' ? 'Division' : 'Subdivision', | ||
contactEmail: "todo@vatsim.me", | ||
website: "https://vatsim.me" | ||
} | ||
}); | ||
|
||
return { | ||
form | ||
} | ||
}, | ||
delete: async (event) => { | ||
let { user } = await loadUserData(event.cookies, null); | ||
if (!user.isSiteAdmin) { | ||
return fail(400, {}); | ||
} | ||
await prisma.facility.delete({ | ||
where: { | ||
id: (await event.request.formData()).get("id")!.toString() | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
<script lang="ts"> | ||
import type { Facility } from "@prisma/client"; | ||
import { writable } from "svelte/store"; | ||
import { | ||
createRender, | ||
createTable, | ||
Render, | ||
Subscribe, | ||
} from "svelte-headless-table"; | ||
import * as Table from "$lib/components/ui/table"; | ||
import * as Dialog from "$lib/components/ui/dialog"; | ||
import * as Form from "$lib/components/ui/form"; | ||
import { Plus } from "lucide-svelte"; | ||
import { Button } from "$lib/components/ui/button"; | ||
import { formSchema, type FormSchema } from "./schema"; | ||
import type { SuperValidated } from "sveltekit-superforms"; | ||
import type { PageData } from "./$types"; | ||
import {toast} from "svelte-sonner"; | ||
import ActionButtons from "./actions.svelte"; | ||
export let data: PageData; | ||
let facilities_store = writable(data.facilities!); | ||
$: $facilities_store = data.facilities!; | ||
const table = createTable(facilities_store); | ||
const columns = table.createColumns([ | ||
table.column({ | ||
accessor: "id", | ||
header: "ID" | ||
}), | ||
table.column({ | ||
accessor: "name", | ||
header: "Name", | ||
}), | ||
table.column({ | ||
accessor: "dotnetId", | ||
header: ".NET ID" | ||
}), | ||
table.column({ | ||
accessor: "dotnetType", | ||
header: ".NET Type" | ||
}), | ||
table.column({ | ||
accessor: ({id}) => id, | ||
header: "", | ||
cell: ({value}) => createRender(ActionButtons, { id: value }) | ||
}) | ||
]); | ||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs } = | ||
table.createViewModel(columns); | ||
let createDialogOpen = false; | ||
export let form: SuperValidated<FormSchema>; | ||
let options = { | ||
onUpdated: ({ form }) => { | ||
if (form.valid) { | ||
createDialogOpen = false; | ||
toast.success("Facility created!"); | ||
} | ||
}, | ||
}; | ||
</script> | ||
|
||
<div class="flex items-center justify-between"> | ||
<h2 class="text-3xl font-bold tracking-tight">Manage Facilities</h2> | ||
<Button | ||
on:click={() => { | ||
createDialogOpen = true; | ||
}}> | ||
<Plus class="mr-2 w-4 h-4" /> | ||
Create | ||
</Button> | ||
</div> | ||
|
||
<Table.Root {...$tableAttrs}> | ||
<Table.Header> | ||
{#each $headerRows as headerRow} | ||
<Subscribe rowAttrs={headerRow.attrs()}> | ||
<Table.Row> | ||
{#each headerRow.cells as cell (cell.id)} | ||
<Subscribe attrs={cell.attrs()} let:attrs props={cell.props()}> | ||
<Table.Head {...attrs}> | ||
<Render of={cell.render()} /> | ||
</Table.Head> | ||
</Subscribe> | ||
{/each} | ||
</Table.Row> | ||
</Subscribe> | ||
{/each} | ||
</Table.Header> | ||
<Table.Body {...$tableBodyAttrs}> | ||
{#each $pageRows as row (row.id)} | ||
<Subscribe rowAttrs={row.attrs()} let:rowAttrs> | ||
<Table.Row {...rowAttrs}> | ||
{#each row.cells as cell (cell.id)} | ||
<Subscribe attrs={cell.attrs()} let:attrs> | ||
<Table.Cell {...attrs}> | ||
<Render of={cell.render()} /> | ||
</Table.Cell> | ||
</Subscribe> | ||
{/each} | ||
</Table.Row> | ||
</Subscribe> | ||
{/each} | ||
</Table.Body> | ||
</Table.Root> | ||
|
||
<Dialog.Root bind:open={createDialogOpen}> | ||
<Dialog.Content class="sm:max-w-[425px]"> | ||
<Dialog.Header> | ||
<Dialog.Title>New Facility</Dialog.Title> | ||
</Dialog.Header> | ||
|
||
<div class="space-y-2"> | ||
<Form.Root | ||
action="?/create" | ||
method="POST" | ||
{form} | ||
schema={formSchema} | ||
{options} | ||
let:config> | ||
<Form.Field {config} name="id"> | ||
<Form.Item> | ||
<Form.Label>Facility ID</Form.Label> | ||
<Form.Input /> | ||
<Form.Validation /> | ||
</Form.Item> | ||
</Form.Field> | ||
<Form.Field {config} name="name"> | ||
<Form.Item> | ||
<Form.Label>Facility Name</Form.Label> | ||
<Form.Input /> | ||
<Form.Validation /> | ||
</Form.Item> | ||
</Form.Field> | ||
<Form.Field {config} name="dotnetId"> | ||
<Form.Item> | ||
<Form.Label>.NET ID</Form.Label> | ||
<Form.Input /> | ||
<Form.Validation /> | ||
</Form.Item> | ||
</Form.Field> | ||
<Form.Item> | ||
<Form.Field {config} name="dotnetType"> | ||
<Form.Label>.NET Type</Form.Label> | ||
<Form.Select> | ||
<Form.SelectTrigger placeholder=".NET Type" /> | ||
<Form.SelectContent> | ||
<Form.SelectItem value="Division">Division</Form.SelectItem> | ||
<Form.SelectItem value="Subdivision">Subdivision</Form.SelectItem> | ||
</Form.SelectContent> | ||
</Form.Select> | ||
<Form.Validation /> | ||
</Form.Field> | ||
</Form.Item> | ||
<Form.Button class="mt-2 w-100"> | ||
<Plus class="mr-2 w-4 h-4" /> | ||
Create | ||
</Form.Button> | ||
</Form.Root> | ||
</div> | ||
</Dialog.Content> | ||
</Dialog.Root> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<script lang="ts"> | ||
import {Button, buttonVariants} from "$lib/components/ui/button"; | ||
import {toast} from "svelte-sonner"; | ||
import * as Dialog from "$lib/components/ui/dialog"; | ||
import {invalidateAll} from "$app/navigation"; | ||
export let id: string; | ||
let deleteOpen = false; | ||
async function removeIt() { | ||
await fetch("?/delete", { | ||
method: 'POST', | ||
body: `id=${id}`, | ||
headers: { | ||
"Content-Type": "application/x-www-form-urlencoded" | ||
} | ||
}); | ||
await invalidateAll(); | ||
toast.success("Facility removed successfully!"); | ||
} | ||
</script> | ||
|
||
<Button href="/{id}/" variant="outline">Open HQ</Button> | ||
<Dialog.Root bind:open={deleteOpen}> | ||
<Dialog.Trigger class={buttonVariants({ variant: "outline" })}>Remove</Dialog.Trigger> | ||
<Dialog.Content class="sm:max-w-[425px]"> | ||
<Dialog.Header> | ||
<Dialog.Title>Are you sure?</Dialog.Title> | ||
<Dialog.Description> | ||
This is an <b>incredibly destructive operation</b>. All data associated with this facility and it's membership information will be permanently obliterated! | ||
</Dialog.Description> | ||
</Dialog.Header> | ||
<Dialog.Footer> | ||
<Button type="submit" on:click={() => {deleteOpen = false;}}>Nevermind</Button> | ||
<Button type="submit" on:click={() => {removeIt(); deleteOpen = false;}} class="bg-red-600 text-red-950 hover:bg-red-700 hover:text-red-950">I'm sure</Button> | ||
</Dialog.Footer> | ||
</Dialog.Content> | ||
</Dialog.Root> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { z } from "zod"; | ||
|
||
export const formSchema = z.object({ | ||
id: z.string(), | ||
name: z.string(), | ||
dotnetId: z.string(), | ||
dotnetType: z.enum(["Subdivision", "Division"]), | ||
}); | ||
|
||
export type FormSchema = typeof formSchema; |