🎉 feat: initialization

This commit is contained in:
web
2025-11-26 19:56:16 -08:00
commit a801849fb2
553 changed files with 213088 additions and 0 deletions
@@ -0,0 +1,213 @@
import { Button } from "@workspace/ui/components/button";
import { Switch } from "@workspace/ui/components/switch";
import { ConfirmButton } from "@workspace/ui/composed/confirm-button";
import {
ProTable,
type ProTableActions,
} from "@workspace/ui/composed/pro-table/pro-table";
import {
createAnnouncement,
deleteAnnouncement,
getAnnouncementList,
updateAnnouncement,
} from "@workspace/ui/services/admin/announcement";
import { format } from "date-fns";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import NoticeForm from "./notice-form";
export default function Page() {
const { t } = useTranslation("announcement");
const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null);
return (
<ProTable<API.Announcement, { enable: boolean; search: string }>
action={ref}
actions={{
render(row) {
return [
<NoticeForm<API.Announcement>
initialValues={row}
key="edit"
loading={loading}
onSubmit={async (values) => {
setLoading(true);
try {
await updateAnnouncement({
...row,
...values,
});
toast.success(t("updateSuccess", "Updated successfully"));
ref.current?.refresh();
setLoading(false);
return true;
} catch {
setLoading(false);
return false;
}
}}
title={t("editAnnouncement", "Edit Announcement")}
trigger={t("edit", "Edit")}
/>,
<ConfirmButton
cancelText={t("cancel", "Cancel")}
confirmText={t("confirm", "Confirm")}
description={t(
"deleteDescription",
"This action cannot be undone."
)}
key="delete"
onConfirm={async () => {
await deleteAnnouncement({
id: row.id,
});
toast.success(t("deleteSuccess", "Deleted successfully"));
ref.current?.refresh();
}}
title={t("confirmDelete", "Confirm Delete")}
trigger={
<Button variant="destructive">{t("delete", "Delete")}</Button>
}
/>,
];
},
batchRender(rows) {
return [
<ConfirmButton
cancelText={t("cancel", "Cancel")}
confirmText={t("confirm", "Confirm")}
description={t(
"deleteDescription",
"This action cannot be undone."
)}
key="delete"
onConfirm={async () => {
for (const element of rows) {
await deleteAnnouncement({
id: element.id!,
});
}
toast.success(t("deleteSuccess", "Deleted successfully"));
ref.current?.refresh();
}}
title={t("confirmDelete", "Confirm Delete")}
trigger={
<Button variant="destructive">{t("delete", "Delete")}</Button>
}
/>,
];
},
}}
columns={[
{
accessorKey: "show",
header: t("show", "Show"),
cell: ({ row }) => (
<Switch
defaultChecked={row.getValue("show")}
onCheckedChange={async (checked) => {
await updateAnnouncement({
...row.original,
show: checked,
});
ref.current?.refresh();
}}
/>
),
},
{
accessorKey: "pinned",
header: t("pinned", "Pinned"),
cell: ({ row }) => (
<Switch
defaultChecked={row.getValue("pinned")}
onCheckedChange={async (checked) => {
await updateAnnouncement({
...row.original,
pinned: checked,
});
ref.current?.refresh();
}}
/>
),
},
{
accessorKey: "popup",
header: t("popup", "Popup"),
cell: ({ row }) => (
<Switch
defaultChecked={row.getValue("popup")}
onCheckedChange={async (checked) => {
await updateAnnouncement({
...row.original,
popup: checked,
});
ref.current?.refresh();
}}
/>
),
},
{
accessorKey: "title",
header: t("title", "Title"),
},
{
accessorKey: "content",
header: t("content", "Content"),
},
{
accessorKey: "updated_at",
header: t("updatedAt", "Updated At"),
cell: ({ row }) =>
format(row.getValue("updated_at"), "yyyy-MM-dd HH:mm:ss"),
},
]}
header={{
title: t("announcementList", "Announcement List"),
toolbar: (
<NoticeForm<API.CreateAnnouncementRequest>
loading={loading}
onSubmit={async (values) => {
setLoading(true);
try {
await createAnnouncement(values);
toast.success(t("createSuccess", "Created successfully"));
ref.current?.refresh();
setLoading(false);
return true;
} catch {
setLoading(false);
return false;
}
}}
title={t("createAnnouncement", "Create Announcement")}
trigger={t("create", "Create")}
/>
),
}}
params={[
{
key: "enable",
placeholder: t("enable", "Enable"),
options: [
{ label: t("show", "Show"), value: "false" },
{ label: t("hide", "Hide"), value: "true" },
],
},
{ key: "search" },
]}
request={async (pagination, filter) => {
const { data } = await getAnnouncementList({
...pagination,
...filter,
});
return {
list: data.data?.list || [],
total: data.data?.total || 0,
};
}}
/>
);
}
@@ -0,0 +1,149 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@workspace/ui/components/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@workspace/ui/components/form";
import { Input } from "@workspace/ui/components/input";
import { ScrollArea } from "@workspace/ui/components/scroll-area";
import {
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@workspace/ui/components/sheet";
import { MarkdownEditor } from "@workspace/ui/composed/editor/markdown";
import { Icon } from "@workspace/ui/composed/icon";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { z } from "zod";
const formSchema = z.object({
title: z.string(),
content: z.string().optional(),
});
interface AnnouncementFormProps<T> {
onSubmit: (data: T) => Promise<boolean> | boolean;
initialValues?: T;
loading?: boolean;
trigger: string;
title: string;
}
export default function AnnouncementForm<T extends Record<string, any>>({
onSubmit,
initialValues,
loading,
trigger,
title,
}: AnnouncementFormProps<T>) {
const { t } = useTranslation("announcement");
const [open, setOpen] = useState(false);
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
title: "",
content: "",
...initialValues,
},
});
useEffect(() => {
form?.reset(initialValues);
}, [form, initialValues]);
async function handleSubmit(data: { [x: string]: any }) {
const bool = await onSubmit(data as T);
if (bool) setOpen(false);
}
return (
<Sheet onOpenChange={setOpen} open={open}>
<SheetTrigger asChild>
<Button
onClick={() => {
form.reset();
setOpen(true);
}}
>
{trigger}
</Button>
</SheetTrigger>
<SheetContent className="w-[800px] max-w-full md:max-w-screen-md">
<SheetHeader>
<SheetTitle>{title}</SheetTitle>
</SheetHeader>
<ScrollArea className="h-[calc(100vh-48px-36px-36px-env(safe-area-inset-top))] px-6">
<Form {...form}>
<form
className="space-y-4 pt-4"
id="notice-form"
onSubmit={form.handleSubmit(handleSubmit)}
>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>{t("form.title", "Title")}</FormLabel>
<FormControl>
<Input
placeholder={t("form.titlePlaceholder", "Enter title")}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormLabel>{t("form.content", "Content")}</FormLabel>
<FormControl>
<MarkdownEditor
onChange={(value) => {
form.setValue(field.name, value || "");
}}
value={field.value}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</ScrollArea>
<SheetFooter className="flex-row justify-end gap-2 pt-3">
<Button
disabled={loading}
onClick={() => {
setOpen(false);
}}
variant="outline"
>
{t("form.cancel", "Cancel")}
</Button>
<Button disabled={loading} onClick={form.handleSubmit(handleSubmit)}>
{loading && (
<Icon className="mr-2 animate-spin" icon="mdi:loading" />
)}{" "}
{t("form.confirm", "Confirm")}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}