11import 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" ;
317import { FormattedMessage , Context } from "@oursky/react-messageformat" ;
418import { useNavigate } from "react-router-dom" ;
19+ import { DateTime , SystemZone } from "luxon" ;
520
621import { useSystemConfig } from "../../context/SystemConfigContext" ;
722import ListCellLayout from "../../ListCellLayout" ;
823import OutlinedActionButton from "../../components/common/OutlinedActionButton" ;
924import PrimaryButton from "../../PrimaryButton" ;
1025import DefaultButton from "../../DefaultButton" ;
26+ import TextField from "../../TextField" ;
1127import ErrorDialog from "../../error/ErrorDialog" ;
1228import { useSetDisabledStatusMutation } from "./mutations/setDisabledStatusMutation" ;
1329import { 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+
3359const dialogStyles = { main : { minHeight : 0 } } ;
3460
3561export 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+
95129export 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 }
0 commit comments