-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-only
import { HamburgerMenuIcon } from "@radix-ui/react-icons";
import {
Box,
type BoxProps,
Container,
Flex,
Grid,
Heading,
IconButton,
ScrollArea,
Text,
} from "@radix-ui/themes";
import { type Worker } from "@yamori/proto/yamori/worker/v1/worker_pb.js";
import { type Workspace } from "@yamori/proto/yamori/workspace/v1/workspace_pb.js";
import { type FC, type ReactNode, use, useCallback, useState } from "react";
import * as CopyrightNotice from "../../components/CopyrightNotice.ts";
import { Logo } from "../../components/Logo.tsx";
import * as NavigationMenu from "../../components/NavigationMenu.ts";
import { URLContext } from "../../contexts/Router.tsx";
import { useViewTransition } from "../../hooks/useViewTransition.ts";
import * as calendar from "./calendar/page.tsx";
import * as paidLeaveProvisionTable from "./paid-leave-provision-table/page.tsx";
import * as workers from "./workers/page.tsx";
import * as workersNew from "./workers/new/page.tsx";
import * as workerDashboard from "./workers/:id/Dashboard.tsx";
import * as leaveDefinitions from "./leave-definitions/page.tsx";
import * as leaveDefinitionsNew from "./leave-definitions/new/page.tsx";
import css from "./Layout.module.css";
export interface LayoutProps extends Pick<BoxProps, "className" | "children" | "style"> {
title: ReactNode;
/**
* この画面 (シーン) にグローバルな操作・アクション。
* Flex コンテナ内となるため、 Fragment で渡すとレイアウトされる。
*/
actions?: ReactNode;
defaultMenuOpened?: boolean;
workspace: Workspace;
worker?: Worker;
}
export const Layout: FC<LayoutProps> = ({
children,
title,
actions,
defaultMenuOpened = false,
workspace,
worker,
...rest
}) => {
const viewTransition = useViewTransition();
const url = use(URLContext);
const [isMenuOpened, setIsMenuOpened] = useState(defaultMenuOpened);
const toggleMenu = useCallback(() => {
viewTransition(() => {
setIsMenuOpened((prev) => !prev);
});
}, [viewTransition]);
return (
<Grid
{...rest}
inset="0"
height="100%"
columns={{ initial: "1", md: "minmax(15rem, max-content) minmax(0, 1fr)" }}
rows="max-content minmax(0, 1fr)"
>
<Flex
className={css.appbar}
display={{ initial: "none", md: "flex" }}
align="center"
justify="center"
>
<a className={css.logo} href="/">
<Logo />
</a>
</Flex>
<Flex className={css.appbar} p="2" align="center" gapX="3">
<Box display={{ md: "none" }}>
<IconButton variant="soft" onClick={toggleMenu}>
<HamburgerMenuIcon />
</IconButton>
</Box>
<Container size={{ initial: "4", md: "2", lg: "3" }}>
<Flex align="center" minHeight="var(--space-6)">
<Box flexGrow="1" flexShrink="1">
<Heading size="3">{title}</Heading>
</Box>
{actions && (
<Flex justify="end" gapX="1">
{actions}
</Flex>
)}
</Flex>
</Container>
</Flex>
<Flex
className={css.menu}
p="2"
gridRowStart="2"
gridColumnStart="1"
display={{ initial: isMenuOpened ? "flex" : "none", md: "flex" }}
direction="column"
gap="2"
>
<Box asChild flexGrow="1" flexShrink="1">
<ScrollArea>
<NavigationMenu.Root>
<NavigationMenu.Group title="労働者管理">
<NavigationMenu.Item current={calendar.pattern.test(url)}>
<a href={calendar.href({ workspace })}>
<calendar.Title />
</a>
</NavigationMenu.Item>
<NavigationMenu.Item current={workers.pattern.test(url)}>
<a href={`/${workspace.id?.value}/workers`}>労働者一覧</a>
</NavigationMenu.Item>
{workspace.workerAddKey && (
<NavigationMenu.Item current={workersNew.pattern.test(url)}>
<a href={`/${workspace.id?.value}/workers/new`}>労働者登録</a>
</NavigationMenu.Item>
)}
{worker && (
<NavigationMenu.Group title={worker.displayName || "労働者メニュー"}>
<NavigationMenu.Item current={workerDashboard.pattern.test(url)}>
<a href={workerDashboard.href({ workspace, worker })}>
<workerDashboard.Title />
</a>
</NavigationMenu.Item>
</NavigationMenu.Group>
)}
</NavigationMenu.Group>
<NavigationMenu.Group title="ワークスペース管理">
<NavigationMenu.Item current={leaveDefinitions.pattern.test(url)}>
<a href={`/${workspace.id?.value}/leave-definitions`}>
休暇・休業種別一覧
</a>
</NavigationMenu.Item>
{workspace.createLeaveDefinitionKey && (
<NavigationMenu.Item current={leaveDefinitionsNew.pattern.test(url)}>
<a href={leaveDefinitionsNew.createHref(workspace)}>
<leaveDefinitionsNew.Title />
</a>
</NavigationMenu.Item>
)}
<NavigationMenu.Item current={paidLeaveProvisionTable.pattern.test(url)}>
<a href={paidLeaveProvisionTable.href({ workspace })}>
<paidLeaveProvisionTable.Title />
</a>
</NavigationMenu.Item>
</NavigationMenu.Group>
</NavigationMenu.Root>
</ScrollArea>
</Box>
<Flex asChild align="center" justify="end" gap="4">
<Text size="1" color="gray">
<CopyrightNotice.ThirdParty />
<CopyrightNotice.FirstParty />
</Text>
</Flex>
</Flex>
<Box
overflowY="auto"
position="relative"
gridRowStart="2"
gridColumnStart={{ initial: "1", md: "2" }}
>
{children}
</Box>
</Grid>
);
};