Changes
5 changed files (+246/-0)
-
-
@@ -249,6 +249,16 @@ failureRate = 0,workspace = { displayName: "Mock Workspace", users: [alice, bob], customAttributeDefinition: [ { id: { value: "cf-foo" }, displayName: "社員ID", }, { id: { value: "cf-bar" }, displayName: "部署", }, ], }, error = { case: "systemError",
-
-
-
@@ -22,6 +22,7 @@ import * as NavigationMenu from "../components/NavigationMenu.ts";import { URLContext } from "../contexts/Router.tsx"; import { useViewTransition } from "../hooks/useViewTransition.ts"; import * as customAttributes from "./custom-attributes/page.tsx"; import * as userNew from "./user/new/page.tsx"; import * as users from "./users/page.tsx";
-
@@ -136,6 +137,11 @@ <userNew.Title /></a> </NavigationMenu.Item> )} <NavigationMenu.Item current={customAttributes.pattern.test(url)}> <a href={customAttributes.createHref()}> <customAttributes.Title /> </a> </NavigationMenu.Item> </NavigationMenu.Group> </NavigationMenu.Root> </ScrollArea>
-
-
-
@@ -0,0 +1,56 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { create } from "@bufbuild/protobuf"; import { UserSchema } from "@yamori/proto/yamori/workspace/v2/user_pb.js"; import type { Meta, StoryObj } from "@storybook/react"; import { withMockedBackend } from "../../../.storybook/decorators/withMockedBackend.tsx"; import { withInmemoryRouter } from "../../../.storybook/decorators/withInmemoryRouter.tsx"; import { alice, bob, Get } from "../../mocks/yamori/workspace/v2/workspace_service.ts"; import { createHref, Page } from "./page.tsx"; export default { component: Page, parameters: { layout: "fullscreen", }, args: { loginUser: alice, }, decorators: [withInmemoryRouter({ initialURL: createHref() })], } satisfies Meta<typeof Page>; type Story = StoryObj<typeof Page>; export const Success: Story = { decorators: [withMockedBackend([Get()])], }; export const Empty: Story = { decorators: [ withMockedBackend([ Get({ workspace: { customAttributeDefinition: [], }, }), ]), ], }; export const NoMutatePermission: Story = { args: { loginUser: bob, }, decorators: [withMockedBackend([Get()])], }; export const SystemError: Story = { decorators: [withMockedBackend([Get({ failureRate: 1 })])], }; export const SlowLoad: Story = { decorators: [withMockedBackend([Get({ delayMs: 2_000 })])], };
-
-
-
@@ -0,0 +1,169 @@// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import "../../polyfill.ts"; import { createClient } from "@connectrpc/connect"; import { Button, Container, Flex, Separator, Spinner, Text } from "@radix-ui/themes"; import { useQuery } from "@tanstack/react-query"; import { type User } from "@yamori/proto/yamori/workspace/v2/user_pb.js"; import { WorkspaceService } from "@yamori/proto/yamori/workspace/v2/workspace_service_pb.js"; import { type FC, Fragment } from "react"; import * as Empty from "../../components/Empty.ts"; import * as HelpDialog from "../../components/HelpDialog.ts"; import { ManagedErrorCallout } from "../../components/ErrorCallout.ts"; import { useConnectTransport } from "../../contexts/ConnectTransport.tsx"; import { IllegalMessageError } from "../../errors/IllegalMessageError.ts"; import { LoggedInLayout } from "../LoggedInLayout.tsx"; export const Title: FC = () => "カスタムフィールド定義一覧"; export const Body: FC = () => { const transport = useConnectTransport(); const client = createClient(WorkspaceService, transport); const defs = useQuery({ queryKey: [ WorkspaceService.typeName, WorkspaceService.method.get.name, import.meta.url, ], async queryFn() { const resp = await client.get({}); if (resp.result.case === "ok") { return resp.result.value.customAttributeDefinition; } if (typeof resp.result.case === "string") { throw resp.result.value; } throw new IllegalMessageError(resp); }, }); if (!defs.data || !defs.data.length) { if (defs.error) { return ( <Empty.Root> <Empty.Title>カスタムフィールド定義の一覧を読み込めませんでした</Empty.Title> <ManagedErrorCallout error={defs.error} title="カスタムフィールド定義の一覧取得中にエラーが発生しました" /> <Empty.Actions> <Button loading={defs.isFetching} onClick={() => void defs.refetch()}> 再取得 </Button> </Empty.Actions> </Empty.Root> ); } if (defs.isFetching) { return ( <Flex mt="5" justify="center" align="center" gap="2"> <Spinner /> <Text size="2" color="gray"> 一覧を取得中... </Text> </Flex> ); } // TODO: 新規作成画面への導線 return ( <Empty.Root> <Empty.Title>カスタムフィールドは定義されていません</Empty.Title> <Empty.Description> システムにカスタムフィールドの定義は登録されていません。 </Empty.Description> <Empty.Description> 社員 ID などの属性を紐づける場合はカスタムフィールドを定義してください。 </Empty.Description> </Empty.Root> ); } // TODO: 編集画面への導線 return ( <Flex direction="column" mt="5" gap="5"> {defs.error && ( <ManagedErrorCallout severity="warning" title="一覧の更新取得に失敗しました" error={defs.error} actions={ <Button size="1" loading={defs.isFetching} onClick={() => void defs.refetch()} > 再試行 </Button> } /> )} <Flex direction="column" gap="4" role="list"> {defs.data.map((def, i) => { return ( <Fragment key={def.id?.value}> {i > 0 && <Separator size="4" />} <Flex direction="column" gap="2" role="listitem"> <Text weight="bold">{def.displayName}</Text> </Flex> </Fragment> ); })} </Flex> </Flex> ); }; export interface PageProps { loginUser: User; } export const Page: FC<PageProps> = ({ loginUser }) => { return ( <HelpDialog.Root> <LoggedInLayout title={ <Flex as="span" align="center" gap="2"> <Title /> <HelpDialog.Trigger>このページの説明</HelpDialog.Trigger> </Flex> } user={loginUser} > <Container p="2" size="2"> <Body /> </Container> </LoggedInLayout> <HelpDialog.Content> <HelpDialog.Title> <Title /> </HelpDialog.Title> <HelpDialog.Description> システムに登録されているユーザの一覧です。 </HelpDialog.Description> <HelpDialog.Paragraph> システム上で定義されているカスタムフィールドの一覧です。 ここで定義されているカスタムフィールドはユーザ情報内の項目として編集できるようになります。 </HelpDialog.Paragraph> </HelpDialog.Content> </HelpDialog.Root> ); }; export function createHref(): string { return "/custom-attributes"; } export const pattern = new URLPattern({ pathname: "/custom-attributes", });
-
-
-
@@ -10,6 +10,7 @@import * as Empty from "../components/Empty.ts"; import { Select } from "../contexts/Router.tsx"; import * as customAttributes from "./custom-attributes/page.tsx"; import * as userNew from "./user/new/page.tsx"; import * as users from "./users/page.tsx"; import * as root from "./root/page.tsx";
-
@@ -32,6 +33,10 @@ },{ pattern: users.pattern, children: <users.Page loginUser={loginUser} />, }, { pattern: customAttributes.pattern, children: <customAttributes.Page loginUser={loginUser} />, }, ]} fallback={
-