差距分析闭环(workline §7-§8): - P0:审计子模块 3 页 + 学校组织 7 页 - P1:系统设置 4 Card + 公告管理 + 邀请码管理 - P2:文件管理 + AI 配置 - P6:5 测试文件 / 69 用例全过 共享基础设施: - permissions +11 权限点 +14 路由 - view-models +20 接口 - i18n +100 key - graphql-client +25 operation - fixtures/handlers +15 mock +20 handler 质量:typecheck + lint 零错误,vitest 69/69 通过,arch.db 已更新
129 lines
3.4 KiB
TypeScript
129 lines
3.4 KiB
TypeScript
/**
|
||
* 公告管理 Hooks
|
||
*
|
||
* contract §2.4:adminAnnouncements / createAnnouncement / publishAnnouncement / archiveAnnouncement / toggleAnnouncementPin
|
||
*/
|
||
import { useCallback, useState } from "react";
|
||
import { useGraphMutation, useGraphQuery } from "./use-graphql";
|
||
import {
|
||
ADMIN_ANNOUNCEMENTS_QUERY,
|
||
CREATE_ANNOUNCEMENT_MUTATION,
|
||
PUBLISH_ANNOUNCEMENT_MUTATION,
|
||
ARCHIVE_ANNOUNCEMENT_MUTATION,
|
||
TOGGLE_ANNOUNCEMENT_PIN_MUTATION,
|
||
} from "@/lib/graphql-client";
|
||
import type {
|
||
AnnouncementViewModel,
|
||
PaginatedResult,
|
||
ListFilter,
|
||
} from "@/types/view-models";
|
||
|
||
interface AdminAnnouncementsResponse {
|
||
adminAnnouncements: PaginatedResult<AnnouncementViewModel>;
|
||
}
|
||
|
||
interface AnnouncementMutationResponse {
|
||
createAnnouncement?: { id: string; title: string; status: string };
|
||
publishAnnouncement?: { id: string; status: string };
|
||
archiveAnnouncement?: { id: string; status: string };
|
||
toggleAnnouncementPin?: { id: string; isPinned: boolean };
|
||
}
|
||
|
||
interface CreateAnnouncementInput {
|
||
title: string;
|
||
content: string;
|
||
audience?: string[];
|
||
}
|
||
|
||
export function useAnnouncements(filter: Partial<ListFilter>) {
|
||
const result = useGraphQuery<AdminAnnouncementsResponse>(
|
||
ADMIN_ANNOUNCEMENTS_QUERY,
|
||
{ filter },
|
||
);
|
||
return {
|
||
...result,
|
||
data: result.data?.adminAnnouncements ?? null,
|
||
};
|
||
}
|
||
|
||
export function useAnnouncementFilter(initial?: Partial<ListFilter>) {
|
||
const [filter, setFilter] = useState<Partial<ListFilter>>({
|
||
page: 1,
|
||
pageSize: 20,
|
||
...initial,
|
||
});
|
||
const setPage = useCallback(
|
||
(page: number) => setFilter((f) => ({ ...f, page })),
|
||
[],
|
||
);
|
||
const setSearch = useCallback(
|
||
(search: string) => setFilter((f) => ({ ...f, search, page: 1 })),
|
||
[],
|
||
);
|
||
const setStatus = useCallback(
|
||
(status: string) => setFilter((f) => ({ ...f, status, page: 1 })),
|
||
[],
|
||
);
|
||
return { filter, setFilter, setPage, setSearch, setStatus };
|
||
}
|
||
|
||
export function useCreateAnnouncement() {
|
||
const [run, state] = useGraphMutation<
|
||
AnnouncementMutationResponse,
|
||
{ input: CreateAnnouncementInput }
|
||
>(CREATE_ANNOUNCEMENT_MUTATION);
|
||
const create = useCallback(
|
||
async (input: CreateAnnouncementInput) => {
|
||
const res = await run({ input });
|
||
return res.data?.createAnnouncement ?? null;
|
||
},
|
||
[run],
|
||
);
|
||
return [create, state] as const;
|
||
}
|
||
|
||
export function usePublishAnnouncement() {
|
||
const [run, state] = useGraphMutation<
|
||
AnnouncementMutationResponse,
|
||
{ id: string }
|
||
>(PUBLISH_ANNOUNCEMENT_MUTATION);
|
||
const publish = useCallback(
|
||
async (id: string) => {
|
||
const res = await run({ id });
|
||
return res.data?.publishAnnouncement ?? null;
|
||
},
|
||
[run],
|
||
);
|
||
return [publish, state] as const;
|
||
}
|
||
|
||
export function useArchiveAnnouncement() {
|
||
const [run, state] = useGraphMutation<
|
||
AnnouncementMutationResponse,
|
||
{ id: string }
|
||
>(ARCHIVE_ANNOUNCEMENT_MUTATION);
|
||
const archive = useCallback(
|
||
async (id: string) => {
|
||
const res = await run({ id });
|
||
return res.data?.archiveAnnouncement ?? null;
|
||
},
|
||
[run],
|
||
);
|
||
return [archive, state] as const;
|
||
}
|
||
|
||
export function useToggleAnnouncementPin() {
|
||
const [run, state] = useGraphMutation<
|
||
AnnouncementMutationResponse,
|
||
{ id: string }
|
||
>(TOGGLE_ANNOUNCEMENT_PIN_MUTATION);
|
||
const toggle = useCallback(
|
||
async (id: string) => {
|
||
const res = await run({ id });
|
||
return res.data?.toggleAnnouncementPin ?? null;
|
||
},
|
||
[run],
|
||
);
|
||
return [toggle, state] as const;
|
||
}
|