Skip to content

Commit 80506fa

Browse files
Layout the new disable form
1 parent 006adb1 commit 80506fa

File tree

2 files changed

+226
-4
lines changed

2 files changed

+226
-4
lines changed

portal/src/graphql/adminapi/UserDetailsAccountStatus.tsx

Lines changed: 218 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
import React, { useContext, useState, useCallback, useMemo } from "react";
2-
import { IStyle, Label, Text, Dialog, DialogFooter } from "@fluentui/react";
2+
import {
3+
IStyle,
4+
Label,
5+
Text,
6+
Dialog,
7+
DialogFooter,
8+
ChoiceGroup,
9+
IChoiceGroupOption,
10+
IChoiceGroupOptionProps,
11+
MessageBar,
12+
MessageBarType,
13+
DatePicker,
14+
TimePicker,
15+
IComboBox,
16+
} from "@fluentui/react";
317
import { FormattedMessage, Context } from "@oursky/react-messageformat";
418
import { useNavigate } from "react-router-dom";
19+
import { DateTime, SystemZone } from "luxon";
520

621
import { useSystemConfig } from "../../context/SystemConfigContext";
722
import ListCellLayout from "../../ListCellLayout";
823
import OutlinedActionButton from "../../components/common/OutlinedActionButton";
924
import PrimaryButton from "../../PrimaryButton";
1025
import DefaultButton from "../../DefaultButton";
26+
import TextField from "../../TextField";
1127
import ErrorDialog from "../../error/ErrorDialog";
1228
import { useSetDisabledStatusMutation } from "./mutations/setDisabledStatusMutation";
1329
import { useAnonymizeUserMutation } from "./mutations/anonymizeUserMutation";
@@ -30,6 +46,16 @@ const bodyTextStyle: IStyle = {
3046
maxWidth: "500px",
3147
};
3248

49+
const choiceGroupStyle = {
50+
flexContainer: {
51+
selectors: {
52+
".ms-ChoiceField": {
53+
display: "block",
54+
},
55+
},
56+
},
57+
};
58+
3359
const dialogStyles = { main: { minHeight: 0 } };
3460

3561
export interface AccountStatus {
@@ -92,6 +118,14 @@ interface ButtonStates {
92118
};
93119
}
94120

121+
function formatSystemZone(now: Date, locale: string): string {
122+
const zone = new SystemZone();
123+
return `${zone.offsetName(now.getTime(), {
124+
format: "short",
125+
locale,
126+
})} (${zone.name})`;
127+
}
128+
95129
export function getMostAppropriateAction(
96130
data: AccountStatus
97131
):
@@ -612,6 +646,183 @@ export function AccountStatusDialog(
612646
const { isHidden, onDismiss, mode, accountStatus } = props;
613647
const buttonStates = getButtonStates(accountStatus);
614648
const { themes } = useSystemConfig();
649+
const { locale, renderToString } = useContext(Context);
650+
651+
const [disableChoiceGroupKey, setDisableChoiceGroupKey] = useState<
652+
"indefinitely" | "temporarily"
653+
>("indefinitely");
654+
const [minDate] = useState(() => {
655+
return new Date();
656+
});
657+
const [maxDate] = useState(() => {
658+
return DateTime.now().plus({ years: 1 }).toJSDate();
659+
});
660+
const [temporarilyDisabledUntil_date, setTemporarilyDisabledUntil_date] =
661+
useState(() => {
662+
return DateTime.now().plus({ days: 1 }).toJSDate();
663+
});
664+
const [temporarilyDisabledUntil_time, setTemporarilyDisabledUntil_time] =
665+
useState(() => {
666+
return DateTime.now()
667+
.plus({ days: 1 })
668+
.set({
669+
minute: 30,
670+
second: 0,
671+
millisecond: 0,
672+
})
673+
.toJSDate();
674+
});
675+
676+
const [disableReason, setDisableReason] = useState("");
677+
const onSelectTemporarilyDisabledUntil_date = useCallback(
678+
(date: Date | null | undefined) => {
679+
if (date == null) {
680+
return;
681+
}
682+
setTemporarilyDisabledUntil_date(date);
683+
},
684+
[]
685+
);
686+
const onChangeTemporarilyDisabledUntil_time = useCallback(
687+
(_e: React.FormEvent<IComboBox>, time: Date) => {
688+
setTemporarilyDisabledUntil_time(time);
689+
},
690+
[]
691+
);
692+
693+
const onRenderTemporarilyDisableFormField = useCallback(
694+
(
695+
props?: IChoiceGroupOption & IChoiceGroupOptionProps,
696+
render?: (
697+
props?: IChoiceGroupOption & IChoiceGroupOptionProps
698+
) => JSX.Element | null
699+
) => {
700+
const formattedZone = formatSystemZone(new Date(), locale);
701+
return (
702+
<div className="flex flex-col gap-2">
703+
{render?.(props)}
704+
<div className="flex flex-col ml-6 gap-2">
705+
<MessageBar
706+
messageBarType={MessageBarType.info}
707+
styles={{
708+
iconContainer: {
709+
display: "none",
710+
},
711+
}}
712+
>
713+
<FormattedMessage
714+
id="AccountStatusDialog.disable-user.timezone-description"
715+
values={{
716+
timezone: formattedZone,
717+
}}
718+
/>
719+
</MessageBar>
720+
<div className="flex flex-row gap-2">
721+
<DatePicker
722+
className="flex-1"
723+
disabled={disableChoiceGroupKey !== "temporarily"}
724+
minDate={minDate}
725+
maxDate={maxDate}
726+
value={temporarilyDisabledUntil_date}
727+
onSelectDate={onSelectTemporarilyDisabledUntil_date}
728+
/>
729+
<TimePicker
730+
className="flex-1"
731+
disabled={disableChoiceGroupKey !== "temporarily"}
732+
allowFreeform={false}
733+
increments={30}
734+
showSeconds={false}
735+
useHour12={false}
736+
value={temporarilyDisabledUntil_time}
737+
onChange={onChangeTemporarilyDisabledUntil_time}
738+
/>
739+
</div>
740+
</div>
741+
</div>
742+
);
743+
},
744+
[
745+
disableChoiceGroupKey,
746+
locale,
747+
maxDate,
748+
minDate,
749+
onChangeTemporarilyDisabledUntil_time,
750+
onSelectTemporarilyDisabledUntil_date,
751+
temporarilyDisabledUntil_date,
752+
temporarilyDisabledUntil_time,
753+
]
754+
);
755+
756+
const disableChoiceGroupOptions: IChoiceGroupOption[] = useMemo(() => {
757+
return [
758+
{
759+
key: "indefinitely",
760+
text: renderToString(
761+
"AccountStatusDialog.disable-user.disable-period.options.indefinitely"
762+
),
763+
},
764+
{
765+
key: "temporarily",
766+
text: renderToString(
767+
"AccountStatusDialog.disable-user.disable-period.options.temporarily"
768+
),
769+
onRenderField: onRenderTemporarilyDisableFormField,
770+
},
771+
];
772+
}, [onRenderTemporarilyDisableFormField, renderToString]);
773+
774+
const onChangeDisableChoiceGroup = useCallback(
775+
(
776+
_?: React.FormEvent<HTMLElement | HTMLInputElement>,
777+
option?: IChoiceGroupOption
778+
) => {
779+
if (!option?.key) return;
780+
setDisableChoiceGroupKey(option.key as any);
781+
},
782+
[]
783+
);
784+
const onChangeDisableReason = useCallback(
785+
(
786+
_e: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
787+
value?: string
788+
) => {
789+
setDisableReason(value ?? "");
790+
},
791+
[]
792+
);
793+
const disableForm = useMemo(() => {
794+
return (
795+
<div className="flex flex-col gap-4">
796+
<ChoiceGroup
797+
styles={choiceGroupStyle}
798+
// @ts-expect-error
799+
label={
800+
<FormattedMessage id="AccountStatusDialog.disable-user.disable-period.label" />
801+
}
802+
options={disableChoiceGroupOptions}
803+
selectedKey={disableChoiceGroupKey}
804+
onChange={onChangeDisableChoiceGroup}
805+
/>
806+
<TextField
807+
// @ts-expect-error
808+
label={
809+
<FormattedMessage id="AccountStatusDialog.disable-user.disable-reason.label" />
810+
}
811+
placeholder={renderToString(
812+
"AccountStatusDialog.disable-user.disable-reason.placeholder"
813+
)}
814+
value={disableReason}
815+
onChange={onChangeDisableReason}
816+
/>
817+
</div>
818+
);
819+
}, [
820+
disableChoiceGroupKey,
821+
disableChoiceGroupOptions,
822+
disableReason,
823+
onChangeDisableChoiceGroup,
824+
onChangeDisableReason,
825+
]);
615826

616827
const {
617828
setDisabledStatus,
@@ -768,6 +979,7 @@ export function AccountStatusDialog(
768979
title: React.ReactElement | null;
769980
subText: React.ReactElement | null;
770981
};
982+
body: React.ReactElement | null;
771983
button1: React.ReactElement | null;
772984
button2: React.ReactElement | null;
773985
} = useMemo(() => {
@@ -778,6 +990,7 @@ export function AccountStatusDialog(
778990

779991
let title: React.ReactElement | null = null;
780992
let subText: React.ReactElement | null = null;
993+
let body: React.ReactElement | null = null;
781994
let button1: React.ReactElement | null = null;
782995
let button2: React.ReactElement | null = null;
783996

@@ -850,6 +1063,7 @@ export function AccountStatusDialog(
8501063
values={args}
8511064
/>
8521065
);
1066+
body = disableForm;
8531067
button1 = (
8541068
<PrimaryButton
8551069
theme={themes.destructive}
@@ -1003,10 +1217,11 @@ export function AccountStatusDialog(
10031217
break;
10041218
}
10051219
}
1006-
return { dialogContentProps: { title, subText }, button1, button2 };
1220+
return { dialogContentProps: { title, subText }, body, button1, button2 };
10071221
}, [
10081222
accountStatus,
10091223
buttonStates.toggleDisable.isDisabledIndefinitelyOrTemporarily,
1224+
disableForm,
10101225
loading,
10111226
mode,
10121227
onClickAnonymize,
@@ -1031,6 +1246,7 @@ export function AccountStatusDialog(
10311246
styles={dialogStyles}
10321247
minWidth={560}
10331248
>
1249+
{dialogContentPropsAndDialogSlots.body}
10341250
<DialogFooter>
10351251
{dialogContentPropsAndDialogSlots.button1}
10361252
{dialogContentPropsAndDialogSlots.button2}

portal/src/locale-data/en.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,10 +457,16 @@
457457
"UserDetailsAccountStatus.remove-user.action.cancel": "Cancel removal",
458458

459459
"AccountStatusDialog.disable-user.title": "Disable User",
460-
"AccountStatusDialog.disable-user.description": "Do you really want to disable user {username}? Disabling the user {username} will forbid them from logging in. Their existing sessions will also be removed.",
460+
"AccountStatusDialog.disable-user.description": "Are you sure you want to disable user {strong, react, children{{username}}}? {br, react} While disabled, the user cannot log in and all active sessions will be terminated.",
461+
"AccountStatusDialog.disable-user.timezone-description": "All times in {timezone}",
462+
"AccountStatusDialog.disable-user.disable-period.label": "Disable Period",
463+
"AccountStatusDialog.disable-user.disable-period.options.indefinitely": "Disable indefinitely",
464+
"AccountStatusDialog.disable-user.disable-period.options.temporarily": "Disable until a specific date",
465+
"AccountStatusDialog.disable-user.disable-reason.label": "Disable reason (optional)",
466+
"AccountStatusDialog.disable-user.disable-reason.placeholder": "Employee on temporary leave",
461467
"AccountStatusDialog.disable-user.action.disable": "Disable",
462468
"AccountStatusDialog.reenable-user.title": "Re-enable User",
463-
"AccountStatusDialog.reenable-user.description": "Do you really want to re-enable user {username}? Re-enable the user {username} so that they can login again.",
469+
"AccountStatusDialog.reenable-user.description": "Are you sure you want to re-enable user {strong, react, children{{username}}}? Re-enable the user {strong, react, children{{username}}} so that they can login again.",
464470
"AccountStatusDialog.reenable-user.action.reenable": "Re-enable",
465471
"AccountStatusDialog.delete-user.title": "Remove User",
466472
"AccountStatusDialog.delete-user.description": "Are you really want to remove user {username}? This action cannot be undone.",

0 commit comments

Comments
 (0)