Created a dialog to select an account
This commit is contained in:
		
							
								
								
									
										82
									
								
								moneymgr_web/src/dialogs/SelectAccountDialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								moneymgr_web/src/dialogs/SelectAccountDialog.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  Dialog,
 | 
				
			||||||
 | 
					  DialogActions,
 | 
				
			||||||
 | 
					  DialogContent,
 | 
				
			||||||
 | 
					  DialogContentText,
 | 
				
			||||||
 | 
					  DialogTitle,
 | 
				
			||||||
 | 
					  FormControlLabel,
 | 
				
			||||||
 | 
					  ListItem,
 | 
				
			||||||
 | 
					  ListItemIcon,
 | 
				
			||||||
 | 
					  ListItemText,
 | 
				
			||||||
 | 
					  Radio,
 | 
				
			||||||
 | 
					  RadioGroup,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { Account } from "../api/AccountApi";
 | 
				
			||||||
 | 
					import { useAccounts } from "../hooks/AccountsListProvider";
 | 
				
			||||||
 | 
					import { AccountWidget } from "../widgets/AccountWidget";
 | 
				
			||||||
 | 
					import { AmountWidget } from "../widgets/AmountWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function SelectAccountDialog(p: {
 | 
				
			||||||
 | 
					  open: boolean;
 | 
				
			||||||
 | 
					  onClose: () => void;
 | 
				
			||||||
 | 
					  onSelected: (c: Account) => void;
 | 
				
			||||||
 | 
					  title: string;
 | 
				
			||||||
 | 
					  description: string;
 | 
				
			||||||
 | 
					  confirmButton?: string;
 | 
				
			||||||
 | 
					  excludedAccounts?: Account[];
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const accounts = useAccounts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [choice, setChoice] = React.useState<Account | null>(null);
 | 
				
			||||||
 | 
					  console.log(choice);
 | 
				
			||||||
 | 
					  const submit = () => {
 | 
				
			||||||
 | 
					    if (choice) p.onSelected(choice);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Dialog open={p.open} onClose={p.onClose}>
 | 
				
			||||||
 | 
					      <DialogTitle>{p.title}</DialogTitle>
 | 
				
			||||||
 | 
					      <DialogContent dividers>
 | 
				
			||||||
 | 
					        <DialogContentText>{p.description}</DialogContentText>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <RadioGroup>
 | 
				
			||||||
 | 
					          {accounts.list.list.map((option) => (
 | 
				
			||||||
 | 
					            <FormControlLabel
 | 
				
			||||||
 | 
					              value={option.id}
 | 
				
			||||||
 | 
					              key={option.id}
 | 
				
			||||||
 | 
					              control={
 | 
				
			||||||
 | 
					                <Radio
 | 
				
			||||||
 | 
					                  disabled={
 | 
				
			||||||
 | 
					                    p.excludedAccounts?.find((a) => a.id === option.id) !==
 | 
				
			||||||
 | 
					                    undefined
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  checked={option.id === choice?.id}
 | 
				
			||||||
 | 
					                  onChange={() => setChoice(option)}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              label={
 | 
				
			||||||
 | 
					                <ListItem>
 | 
				
			||||||
 | 
					                  <ListItemIcon>
 | 
				
			||||||
 | 
					                    <AccountWidget account={option} />
 | 
				
			||||||
 | 
					                  </ListItemIcon>
 | 
				
			||||||
 | 
					                  <ListItemText
 | 
				
			||||||
 | 
					                    primary={option.name}
 | 
				
			||||||
 | 
					                    secondary={<AmountWidget amount={option.balance} />}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </ListItem>
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					        </RadioGroup>
 | 
				
			||||||
 | 
					      </DialogContent>
 | 
				
			||||||
 | 
					      <DialogActions>
 | 
				
			||||||
 | 
					        <Button onClick={p.onClose}>Cancel</Button>
 | 
				
			||||||
 | 
					        <Button onClick={submit} autoFocus disabled={choice === null}>
 | 
				
			||||||
 | 
					          {p.confirmButton ?? "Submit"}
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </DialogActions>
 | 
				
			||||||
 | 
					    </Dialog>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { Account } from "../../api/AccountApi";
 | 
				
			||||||
 | 
					import { SelectAccountDialog } from "../../dialogs/SelectAccountDialog";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DialogContext = (
 | 
				
			||||||
 | 
					  title: string,
 | 
				
			||||||
 | 
					  description: string,
 | 
				
			||||||
 | 
					  confirmButton?: string,
 | 
				
			||||||
 | 
					  excludedAccounts?: Account[]
 | 
				
			||||||
 | 
					) => Promise<Account | undefined>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DialogContextK = React.createContext<DialogContext | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ChooseAccountDialogProvider(
 | 
				
			||||||
 | 
					  p: React.PropsWithChildren
 | 
				
			||||||
 | 
					): React.ReactElement {
 | 
				
			||||||
 | 
					  const [title, setTitle] = React.useState("");
 | 
				
			||||||
 | 
					  const [description, setDescription] = React.useState("");
 | 
				
			||||||
 | 
					  const [confirmButton, setConfirmButton] = React.useState<
 | 
				
			||||||
 | 
					    string | undefined
 | 
				
			||||||
 | 
					  >();
 | 
				
			||||||
 | 
					  const [excludedAccounts, setExcludedAccounts] = React.useState<
 | 
				
			||||||
 | 
					    Account[] | undefined
 | 
				
			||||||
 | 
					  >();
 | 
				
			||||||
 | 
					  const [open, setOpen] = React.useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cb = React.useRef<null | ((a: Account | undefined) => void)>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleClose = (res?: Account) => {
 | 
				
			||||||
 | 
					    setOpen(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (cb.current !== null) cb.current(res);
 | 
				
			||||||
 | 
					    cb.current = null;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const hook: DialogContext = (
 | 
				
			||||||
 | 
					    title,
 | 
				
			||||||
 | 
					    description,
 | 
				
			||||||
 | 
					    confirmButton,
 | 
				
			||||||
 | 
					    excludedAccounts
 | 
				
			||||||
 | 
					  ) => {
 | 
				
			||||||
 | 
					    setTitle(title);
 | 
				
			||||||
 | 
					    setDescription(description);
 | 
				
			||||||
 | 
					    setConfirmButton(confirmButton);
 | 
				
			||||||
 | 
					    setExcludedAccounts(excludedAccounts);
 | 
				
			||||||
 | 
					    setOpen(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Promise((res) => {
 | 
				
			||||||
 | 
					      cb.current = res;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <DialogContextK.Provider value={hook}>
 | 
				
			||||||
 | 
					        {p.children}
 | 
				
			||||||
 | 
					      </DialogContextK.Provider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {open && (
 | 
				
			||||||
 | 
					        <SelectAccountDialog
 | 
				
			||||||
 | 
					          open={open}
 | 
				
			||||||
 | 
					          onClose={handleClose}
 | 
				
			||||||
 | 
					          onSelected={handleClose}
 | 
				
			||||||
 | 
					          title={title}
 | 
				
			||||||
 | 
					          description={description}
 | 
				
			||||||
 | 
					          confirmButton={confirmButton}
 | 
				
			||||||
 | 
					          excludedAccounts={excludedAccounts}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useSelectAccount(): DialogContext {
 | 
				
			||||||
 | 
					  return React.useContext(DialogContextK)!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -16,6 +16,7 @@ import { DateWidget } from "../widgets/DateWidget";
 | 
				
			|||||||
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
 | 
					import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
 | 
				
			||||||
import { NewMovementWidget } from "../widgets/NewMovementWidget";
 | 
					import { NewMovementWidget } from "../widgets/NewMovementWidget";
 | 
				
			||||||
import { NotFoundRoute } from "./NotFound";
 | 
					import { NotFoundRoute } from "./NotFound";
 | 
				
			||||||
 | 
					import { useSelectAccount } from "../hooks/context_providers/ChooseAccountDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function AccountRoute(): React.ReactElement {
 | 
					export function AccountRoute(): React.ReactElement {
 | 
				
			||||||
  const loadingMessage = useLoadingMessage();
 | 
					  const loadingMessage = useLoadingMessage();
 | 
				
			||||||
@@ -87,12 +88,23 @@ function MovementsTable(p: {
 | 
				
			|||||||
  movements: Movement[];
 | 
					  movements: Movement[];
 | 
				
			||||||
  needReload: (skipMovements: boolean) => void;
 | 
					  needReload: (skipMovements: boolean) => void;
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const accounts = useAccounts();
 | 
				
			||||||
  const alert = useAlert();
 | 
					  const alert = useAlert();
 | 
				
			||||||
  const confirm = useConfirm();
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const chooseAccount = useSelectAccount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Change account of movement
 | 
					  // Change account of movement
 | 
				
			||||||
  const handleMoveClick = async (movement: Movement) => {
 | 
					  const handleMoveClick = async (movement: Movement) => {
 | 
				
			||||||
 | 
					    const target = await chooseAccount(
 | 
				
			||||||
 | 
					      "Transfer movement",
 | 
				
			||||||
 | 
					      `Please select the target account that will receive the movement: ${movement.label} (${movement.amount} €)`,
 | 
				
			||||||
 | 
					      "Transfer movement",
 | 
				
			||||||
 | 
					      [accounts.get(movement.account_id)!]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO
 | 
					    // TODO
 | 
				
			||||||
 | 
					    alert(target?.id?.toString() ?? "none");
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Delete movement
 | 
					  // Delete movement
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import { AccountsListProvider } from "../hooks/AccountsListProvider";
 | 
				
			|||||||
import { AsyncWidget } from "./AsyncWidget";
 | 
					import { AsyncWidget } from "./AsyncWidget";
 | 
				
			||||||
import { MoneyNavList } from "./MoneyNavList";
 | 
					import { MoneyNavList } from "./MoneyNavList";
 | 
				
			||||||
import { MoneyWebAppBar } from "./MoneyWebAppBar";
 | 
					import { MoneyWebAppBar } from "./MoneyWebAppBar";
 | 
				
			||||||
 | 
					import { ChooseAccountDialogProvider } from "../hooks/context_providers/ChooseAccountDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface AuthInfoContext {
 | 
					interface AuthInfoContext {
 | 
				
			||||||
  info: AuthInfo;
 | 
					  info: AuthInfo;
 | 
				
			||||||
@@ -48,44 +49,46 @@ export function BaseAuthenticatedPage(): React.ReactElement {
 | 
				
			|||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <AccountsListProvider>
 | 
					          <AccountsListProvider>
 | 
				
			||||||
            <Box
 | 
					            <ChooseAccountDialogProvider>
 | 
				
			||||||
              component="div"
 | 
					 | 
				
			||||||
              sx={{
 | 
					 | 
				
			||||||
                minHeight: "100vh",
 | 
					 | 
				
			||||||
                display: "flex",
 | 
					 | 
				
			||||||
                flexDirection: "column",
 | 
					 | 
				
			||||||
                backgroundColor: (theme) =>
 | 
					 | 
				
			||||||
                  theme.palette.mode === "light"
 | 
					 | 
				
			||||||
                    ? theme.palette.grey[100]
 | 
					 | 
				
			||||||
                    : theme.palette.grey[900],
 | 
					 | 
				
			||||||
                color: (theme) =>
 | 
					 | 
				
			||||||
                  theme.palette.mode === "light"
 | 
					 | 
				
			||||||
                    ? theme.palette.grey[900]
 | 
					 | 
				
			||||||
                    : theme.palette.grey[100],
 | 
					 | 
				
			||||||
              }}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <MoneyWebAppBar onSignOut={signOut} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              <Box
 | 
					              <Box
 | 
				
			||||||
 | 
					                component="div"
 | 
				
			||||||
                sx={{
 | 
					                sx={{
 | 
				
			||||||
 | 
					                  minHeight: "100vh",
 | 
				
			||||||
                  display: "flex",
 | 
					                  display: "flex",
 | 
				
			||||||
                  flex: "2",
 | 
					                  flexDirection: "column",
 | 
				
			||||||
 | 
					                  backgroundColor: (theme) =>
 | 
				
			||||||
 | 
					                    theme.palette.mode === "light"
 | 
				
			||||||
 | 
					                      ? theme.palette.grey[100]
 | 
				
			||||||
 | 
					                      : theme.palette.grey[900],
 | 
				
			||||||
 | 
					                  color: (theme) =>
 | 
				
			||||||
 | 
					                    theme.palette.mode === "light"
 | 
				
			||||||
 | 
					                      ? theme.palette.grey[900]
 | 
				
			||||||
 | 
					                      : theme.palette.grey[100],
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <MoneyNavList />
 | 
					                <MoneyWebAppBar onSignOut={signOut} />
 | 
				
			||||||
                <div
 | 
					
 | 
				
			||||||
                  style={{
 | 
					                <Box
 | 
				
			||||||
                    flexGrow: 1,
 | 
					                  sx={{
 | 
				
			||||||
                    flexShrink: 0,
 | 
					 | 
				
			||||||
                    flexBasis: 0,
 | 
					 | 
				
			||||||
                    minWidth: 0,
 | 
					 | 
				
			||||||
                    display: "flex",
 | 
					                    display: "flex",
 | 
				
			||||||
 | 
					                    flex: "2",
 | 
				
			||||||
                  }}
 | 
					                  }}
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <Outlet />
 | 
					                  <MoneyNavList />
 | 
				
			||||||
                </div>
 | 
					                  <div
 | 
				
			||||||
 | 
					                    style={{
 | 
				
			||||||
 | 
					                      flexGrow: 1,
 | 
				
			||||||
 | 
					                      flexShrink: 0,
 | 
				
			||||||
 | 
					                      flexBasis: 0,
 | 
				
			||||||
 | 
					                      minWidth: 0,
 | 
				
			||||||
 | 
					                      display: "flex",
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <Outlet />
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </Box>
 | 
				
			||||||
              </Box>
 | 
					              </Box>
 | 
				
			||||||
            </Box>
 | 
					            </ChooseAccountDialogProvider>
 | 
				
			||||||
          </AccountsListProvider>
 | 
					          </AccountsListProvider>
 | 
				
			||||||
        </AuthInfoContextK>
 | 
					        </AuthInfoContextK>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user