Skip to content

Commit f697c21

Browse files
committed
refactor to set up tabs and remove edit icon
1 parent 0dcf072 commit f697c21

File tree

1 file changed

+44
-135
lines changed

1 file changed

+44
-135
lines changed
Lines changed: 44 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,67 @@
11
import { useQuery } from '@apollo/client';
2-
import { Edit } from '@mui/icons-material';
3-
import { Box, Skeleton, Stack, Tooltip, Typography } from '@mui/material';
4-
import { useInterval } from 'ahooks';
5-
import { DateTime } from 'luxon';
6-
import { useState } from 'react';
2+
import { TabContext, TabList, TabPanel } from '@mui/lab';
3+
import { Box, Skeleton, Stack, Typography } from '@mui/material';
74
import { Helmet } from 'react-helmet-async';
85
import { useParams } from 'react-router-dom';
96
import { PartialDeep } from 'type-fest';
10-
import { RoleLabels } from '~/api/schema.graphql';
11-
import { canEditAny, labelsFrom } from '~/common';
127
import { ToggleCommentsButton } from '~/components/Comments/ToggleCommentButton';
8+
import { Error } from '~/components/Error';
9+
import { Redacted } from '~/components/Redacted';
10+
import { Tab, TabsContainer } from '~/components/Tabs';
11+
import { TogglePinButton } from '~/components/TogglePinButton';
12+
import { EnumParam, makeQueryHandler, withDefault } from '~/hooks';
1313
import { useComments } from '../../../components/Comments/CommentsContext';
14-
import { useDialog } from '../../../components/Dialog';
15-
import {
16-
DisplaySimpleProperty,
17-
DisplaySimplePropertyProps,
18-
} from '../../../components/DisplaySimpleProperty';
19-
import { IconButton } from '../../../components/IconButton';
20-
import { PartnerListItemCard } from '../../../components/PartnerListItemCard';
21-
import { Redacted } from '../../../components/Redacted';
22-
import { TogglePinButton } from '../../../components/TogglePinButton';
23-
import { EditUser } from '../Edit';
2414
import { UsersQueryVariables } from '../List/users.graphql';
2515
import { ImpersonationToggle } from './ImpersonationToggle';
16+
import { UserDetailProfile } from './Tabs/Profile/UserDetailProfile';
17+
import { UserDetailProjects } from './Tabs/Projects/UserDetailProjects';
2618
import { UserDocument } from './UserDetail.graphql';
2719

20+
const useUserDetailsFilters = makeQueryHandler({
21+
tab: withDefault(EnumParam(['profile', 'projects']), 'profile'),
22+
});
23+
2824
export const UserDetail = () => {
2925
const { userId = '' } = useParams();
3026
const { data, error } = useQuery(UserDocument, {
3127
variables: { userId },
3228
});
3329
useComments(userId);
34-
35-
const [editUserState, editUser] = useDialog();
36-
30+
const [filters, setFilters] = useUserDetailsFilters();
3731
const user = data?.user;
3832

39-
const canEditAnyFields = canEditAny(user);
40-
4133
return (
4234
<Stack
4335
component="main"
4436
sx={{
4537
overflowY: 'auto',
4638
p: 4,
4739
gap: 3,
48-
maxWidth: (theme) => theme.breakpoints.values.md,
40+
flex: 1,
41+
maxWidth: (theme) => theme.breakpoints.values.xl,
4942
}}
5043
>
5144
<Helmet title={user?.fullName ?? undefined} />
52-
{error ? (
53-
<Typography variant="h4">Error loading person</Typography>
54-
) : (
45+
46+
<Error error={error}>
47+
{{
48+
NotFound: 'Could not find user',
49+
Default: 'Error loading user',
50+
}}
51+
</Error>
52+
{!error && (
5553
<>
5654
<Box
5755
sx={{
58-
flex: 1,
5956
display: 'flex',
6057
gap: 1,
6158
}}
6259
>
6360
<Typography
6461
variant="h2"
6562
sx={{
66-
mr: 2, // a little extra between text and buttons
67-
lineHeight: 'inherit', // centers text with buttons better
63+
mr: 2,
64+
lineHeight: 'inherit',
6865
}}
6966
>
7067
{!user ? (
@@ -78,13 +75,6 @@ export const UserDetail = () => {
7875
)
7976
)}
8077
</Typography>
81-
{canEditAnyFields ? (
82-
<Tooltip title="Edit Person">
83-
<IconButton aria-label="edit person" onClick={editUser}>
84-
<Edit />
85-
</IconButton>
86-
</Tooltip>
87-
) : null}
8878
<TogglePinButton
8979
object={user}
9080
label="Person"
@@ -96,107 +86,26 @@ export const UserDetail = () => {
9686
<ToggleCommentsButton loading={!user} />
9787
<ImpersonationToggle user={user} />
9888
</Box>
99-
<DisplayProperty
100-
label="Status"
101-
value={user?.status.value}
102-
loading={!user}
103-
/>
104-
<DisplayProperty
105-
label="Email"
106-
value={user?.email.value}
107-
loading={!user}
108-
/>
109-
<DisplayProperty
110-
label="Title"
111-
value={user?.title.value}
112-
loading={!user}
113-
/>
114-
<DisplayProperty
115-
label="Roles"
116-
value={labelsFrom(RoleLabels)(user?.roles.value)}
117-
loading={!user}
118-
/>
119-
<DisplayProperty
120-
label="Local Time"
121-
value={
122-
user?.timezone.value?.name ? (
123-
<LocalTime timezone={user.timezone.value.name} />
124-
) : null
125-
}
126-
loading={!user}
127-
/>
128-
<DisplayProperty
129-
label="Phone"
130-
value={user?.phone.value}
131-
loading={!user}
132-
/>
133-
<DisplayProperty
134-
label="About"
135-
value={user?.about.value}
136-
loading={!user}
137-
/>
138-
{user ? <EditUser user={user} {...editUserState} /> : null}
139-
140-
{!!user?.partners.items.length && (
141-
<>
142-
<Typography variant="h3">Partners</Typography>
143-
<Stack sx={{ mt: 1, gap: 2 }}>
144-
{user.partners.items.map((item) => (
145-
<PartnerListItemCard key={item.id} partner={item} />
146-
))}
147-
</Stack>
148-
</>
149-
)}
89+
<TabsContainer>
90+
<TabContext value={filters.tab}>
91+
<TabList
92+
onChange={(_e, tab) => setFilters({ ...filters, tab })}
93+
aria-label="user navigation tabs"
94+
variant="scrollable"
95+
>
96+
<Tab label="Profile" value="profile" />
97+
<Tab label="Projects" value="projects" />
98+
</TabList>
99+
<TabPanel value="profile">
100+
{user && <UserDetailProfile user={user} />}
101+
</TabPanel>
102+
<TabPanel value="projects">
103+
<UserDetailProjects />
104+
</TabPanel>
105+
</TabContext>
106+
</TabsContainer>
150107
</>
151108
)}
152109
</Stack>
153110
);
154111
};
155-
156-
const LocalTime = ({ timezone }: { timezone?: string }) => {
157-
const now = useNow();
158-
const formatted = now.toLocaleString({
159-
timeZone: timezone,
160-
...DateTime.TIME_SIMPLE,
161-
timeZoneName: 'short',
162-
});
163-
return <>{formatted}</>;
164-
};
165-
166-
const useNow = (updateInterval = 1_000) => {
167-
const [now, setNow] = useState(() => DateTime.local());
168-
useInterval(() => {
169-
setNow(DateTime.local());
170-
}, updateInterval);
171-
return now;
172-
};
173-
174-
const DisplayProperty = (props: DisplaySimplePropertyProps) =>
175-
!props.value && !props.loading ? null : (
176-
<DisplaySimpleProperty
177-
variant="body1"
178-
{...{ component: 'div' }}
179-
{...props}
180-
loading={
181-
props.loading ? (
182-
<>
183-
<Typography variant="body2">
184-
<Skeleton width="10%" />
185-
</Typography>
186-
<Typography variant="body1">
187-
<Skeleton width="40%" />
188-
</Typography>
189-
</>
190-
) : null
191-
}
192-
LabelProps={{
193-
color: 'textSecondary',
194-
variant: 'body2',
195-
...props.LabelProps,
196-
}}
197-
ValueProps={{
198-
color: 'textPrimary',
199-
...props.ValueProps,
200-
}}
201-
/>
202-
);

0 commit comments

Comments
 (0)