Skip to content

Commit

Permalink
partially implemented program manager
Browse files Browse the repository at this point in the history
  • Loading branch information
KishinZW committed Jul 28, 2024
1 parent 8463651 commit 9f371a9
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 2 deletions.
2 changes: 1 addition & 1 deletion pages/device.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
</TableCell>
<TableCell>
<div class="flex">
<p class="w-48">
<p class="w-48 truncate">
{{ device.location }}
</p>
<Dialog>
Expand Down
247 changes: 247 additions & 0 deletions pages/program.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
<template>
<div class="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4">
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Total Revenue
</CardTitle>
<DollarSign class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
$45,231.89
</div>
<p class="text-xs text-muted-foreground">
+20.1% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Subscriptions
</CardTitle>
<Users class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+2350
</div>
<p class="text-xs text-muted-foreground">
+180.1% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Sales
</CardTitle>
<CreditCard class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+12,234
</div>
<p class="text-xs text-muted-foreground">
+19% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle class="text-sm font-medium">
Active Now
</CardTitle>
<Activity class="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
+573
</div>
<p class="text-xs text-muted-foreground">
+201 since last hour
</p>
</CardContent>
</Card>
</div>
<div class="grid gap-4 md:gap-8 lg:grid-cols-2 xl:grid-cols-3">
<Card class="xl:col-span-2">
<CardHeader class="flex flex-row items-center">
<div class="grid gap-2">
<CardTitle>所有节目</CardTitle>
</div>
<Dialog>
<DialogTrigger as-child>
<Button variant="outline" class="ml-auto">
创建节目
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>创建节目</DialogTitle>
<DialogDescription>
请输入节目名称
</DialogDescription>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">
节目名称
</Label>
<Input id="name" v-model="name" class="col-span-3" />
</div>
</div>
<DialogClose>
<Button v-if="!isPending" type="submit" @click="createMutation({ name })">
创建节目
</Button>
<Button v-if="isPending" type="submit" disabled>
<Loader2 v-if="isPending" class="w-4 h-4 mr-2 animate-spin" />
请稍候……
</Button>
</DialogClose>
</DialogContent>
</Dialog>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>
节目id
</TableHead>
<TableHead class="w-64">
节目名称
</TableHead>
<TableHead>
创建时间
</TableHead>
<TableHead>
节目内容
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="program in list" :key="program.id">
<TableCell>
{{ program.id }}
</TableCell>
<TableCell>
<div class="flex">
<p class="w-48">
{{ program.name }}
</p>
<Dialog>
<DialogTrigger as-child>
<Pencil
class="opacity-35 flex-initial w-5 text-right"
:size="12"
/>
</DialogTrigger>
<DialogContent class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>编辑节目名</DialogTitle>
<DialogDescription>
请输入新的节目名
</DialogDescription>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">
节目名称
</Label>
<Input id="name" v-model="edit_new_name" class="col-span-3" />
</div>
</div>
<DialogClose>
<Button
v-if="!isPending"
type="submit"
@click="editMutation({ id: program.id, new_name: edit_new_name })"
>
确认修改
</Button>
<Button v-if="isPending" type="submit" disabled>
<Loader2 v-if="isPending" class="w-4 h-4 mr-2 animate-spin" />
请稍候……
</Button>
</DialogClose>
</DialogContent>
</Dialog>
<Trash2
class="opacity-35 flex-initial w-5 text-right"
:size="12"
@click="deleteMutation({ id: program.id })"
/>
</div>
</TableCell>
<TableCell>{{ program.createdAt.toLocaleDateString() }}</TableCell>
<TableCell>
施工中
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
</template>

<script setup lang="ts">
import {
Activity,
CreditCard,
DollarSign,
Loader2,
Pencil,
Trash2,
Users,
} from 'lucide-vue-next';
import { toast } from 'vue-sonner';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
const { $api } = useNuxtApp();
const queryClient = useQueryClient();
const { data: list, suspense } = useQuery({
queryKey: ['program.list'],
queryFn: () => $api.program.list.query(),
});
await suspense();
const name = ref('');
const edit_new_name = ref('');
const { mutate: createMutation, isPending } = useMutation({
mutationFn: $api.program.create.mutate,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['program.list'] });
toast.success('节目创建成功');
},
onError: err => useErrorHandler(err),
});
const { mutate: deleteMutation } = useMutation({
mutationFn: $api.program.delete.mutate,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['program.list'] });
toast.success('节目删除成功');
},
onError: err => useErrorHandler(err),
});
const { mutate: editMutation } = useMutation({
mutationFn: $api.program.edit.mutate,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['program.list'] });
toast.success('修改节目名成功');
},
onError: err => useErrorHandler(err),
});
</script>
3 changes: 2 additions & 1 deletion server/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import { env } from '../env';

import type { devices, users } from './schema';
import type { devices, programs, users } from './schema';
import * as schema from './schema';

const options = (() => {
Expand All @@ -18,3 +18,4 @@ export const db = drizzle(client, { schema });
export type TRawUser = typeof users.$inferSelect;
export type TNewUser = typeof users.$inferInsert;
export type TNewDevice = typeof devices.$inferInsert;
export type TNewProgram = typeof programs.$inferInsert;
6 changes: 6 additions & 0 deletions server/trpc/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@ import type { inferAsyncReturnType } from '@trpc/server';
import { type TRawUser, db } from '../db/db';
import { UserController } from './controllers/user';
import { DeviceController } from './controllers/device';
import { ProgramController } from './controllers/program';

const newGlobal = globalThis as unknown as {
userController: UserController | undefined;
deviceController: DeviceController | undefined;
programController: ProgramController | undefined;
};

const userController = newGlobal.userController ?? new UserController();
const deviceController = newGlobal.deviceController ?? new DeviceController();
const programController = newGlobal.programController ?? new ProgramController();

if (process.env.NODE_ENV !== 'production') {
newGlobal.userController = userController;
newGlobal.deviceController = deviceController;
newGlobal.programController = programController;
}

interface CreateContextOptions {
Expand All @@ -33,12 +37,14 @@ export function createInnerContext(opts: CreateContextOptions) {
user: opts.user,
userController,
deviceController,
programController,
};
}

export const ctl = {
uc: userController,
dc: deviceController,
pc: programController,
};

/**
Expand Down
28 changes: 28 additions & 0 deletions server/trpc/controllers/program.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { eq } from 'drizzle-orm';
import type { TNewProgram } from '../../db/db';
import { db } from '../../db/db';
import { programs } from '../../db/schema';

export class ProgramController {
async create(newProgram: TNewProgram) {
await db.insert(programs).values(newProgram);
return '节目创建成功';
}

async delete(id: number) {
await db.delete(programs).where(eq(programs.id, id));
return '节目删除成功';
}

async edit(id: number, new_name: string) {
await db.update(programs)
.set({ name: new_name })
.where(eq(programs.id, id));
return '节目名修改成功';
}

async getList() {
const res = await db.query.programs.findMany();
return res;
}
}
2 changes: 2 additions & 0 deletions server/trpc/routers/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { router } from '../trpc';
import { userRouter } from './user';
import { deviceRouter } from './device';
import { programRouter } from './program';

export const appRouter = router({
user: userRouter,
device: deviceRouter,
program: programRouter,
});

export type AppRouter = typeof appRouter;
37 changes: 37 additions & 0 deletions server/trpc/routers/program.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { z } from 'zod';
import { protectedProcedure, requireRoles, router } from '../trpc';

const nameZod = z.string()
.max(30, { message: '节目名不能超过30个字符' });
const programIdZod = z.number();

export const programRouter = router({
create: protectedProcedure
.use(requireRoles(['admin']))
.input(z.object({ name: nameZod }))
.mutation(async ({ ctx, input }) => {
return await ctx.programController.create(
{ name: input.name, sequence: [] },
);
}),

delete: protectedProcedure
.use(requireRoles(['admin']))
.input(z.object({ id: programIdZod }))
.mutation(async ({ ctx, input }) => {
return await ctx.programController.delete(input.id);
}),

edit: protectedProcedure
.use(requireRoles(['admin']))
.input(z.object({ id: programIdZod, new_name: nameZod }))
.mutation(async ({ ctx, input }) => {
return await ctx.deviceController.edit(input.id, input.new_name);
}),

list: protectedProcedure
.use(requireRoles(['admin']))
.query(async ({ ctx }) => {
return await ctx.programController.getList();
}),
});

0 comments on commit 9f371a9

Please sign in to comment.