Add a widget to select a movement
This commit is contained in:
		@@ -22,10 +22,55 @@ pub async fn get_accounts_balances(auth: AuthExtractor) -> HttpResult {
 | 
				
			|||||||
    Ok(HttpResponse::Ok().json(movements_service::get_balances(auth.user_id()).await?))
 | 
					    Ok(HttpResponse::Ok().json(movements_service::get_balances(auth.user_id()).await?))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Select movements filter
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct SelectMovementFilters {
 | 
				
			||||||
 | 
					    amount_min: Option<f32>,
 | 
				
			||||||
 | 
					    amount_max: Option<f32>,
 | 
				
			||||||
 | 
					    time_min: Option<i64>,
 | 
				
			||||||
 | 
					    time_max: Option<i64>,
 | 
				
			||||||
 | 
					    label: Option<String>,
 | 
				
			||||||
 | 
					    limit: Option<usize>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get the list of movements of an account
 | 
					/// Get the list of movements of an account
 | 
				
			||||||
pub async fn get_list_of_account(account_id: AccountInPath) -> HttpResult {
 | 
					pub async fn get_list_of_account(
 | 
				
			||||||
    Ok(HttpResponse::Ok()
 | 
					    account_id: AccountInPath,
 | 
				
			||||||
        .json(movements_service::get_list_account(account_id.as_ref().id()).await?))
 | 
					    query: web::Query<SelectMovementFilters>,
 | 
				
			||||||
 | 
					) -> HttpResult {
 | 
				
			||||||
 | 
					    let mut list = movements_service::get_list_account(account_id.as_ref().id()).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(amount_min) = query.amount_min {
 | 
				
			||||||
 | 
					        list.retain(|l| l.amount >= amount_min);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(amount_max) = query.amount_max {
 | 
				
			||||||
 | 
					        list.retain(|l| l.amount <= amount_max);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(time_min) = query.time_min {
 | 
				
			||||||
 | 
					        list.retain(|l| l.time >= time_min);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(time_max) = query.time_max {
 | 
				
			||||||
 | 
					        list.retain(|l| l.time <= time_max);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(label) = &query.label {
 | 
				
			||||||
 | 
					        list.retain(|l| {
 | 
				
			||||||
 | 
					            l.label
 | 
				
			||||||
 | 
					                .to_lowercase()
 | 
				
			||||||
 | 
					                .contains(label.to_lowercase().as_str())
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(limit) = query.limit {
 | 
				
			||||||
 | 
					        if list.len() > limit {
 | 
				
			||||||
 | 
					            list = list[..limit].to_vec();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(list))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get a single movement information
 | 
					/// Get a single movement information
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,10 +50,34 @@ export class MovementApi {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get all the movements of an account
 | 
					   * Get all the movements of an account
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  static async GetAccountMovements(account_id: number): Promise<Movement[]> {
 | 
					  static async GetAccountMovements(
 | 
				
			||||||
 | 
					    account_id: number,
 | 
				
			||||||
 | 
					    filters?: {
 | 
				
			||||||
 | 
					      amount_min?: number;
 | 
				
			||||||
 | 
					      amount_max?: number;
 | 
				
			||||||
 | 
					      time_min?: number;
 | 
				
			||||||
 | 
					      time_max?: number;
 | 
				
			||||||
 | 
					      label?: string;
 | 
				
			||||||
 | 
					      limit?: number;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ): Promise<Movement[]> {
 | 
				
			||||||
 | 
					    let filtersS = new URLSearchParams();
 | 
				
			||||||
 | 
					    if (filters) {
 | 
				
			||||||
 | 
					      if (filters.amount_min)
 | 
				
			||||||
 | 
					        filtersS.append("amount_min", filters.amount_min.toString());
 | 
				
			||||||
 | 
					      if (filters.amount_max)
 | 
				
			||||||
 | 
					        filtersS.append("amount_max", filters.amount_max.toString());
 | 
				
			||||||
 | 
					      if (filters.time_min)
 | 
				
			||||||
 | 
					        filtersS.append("time_min", filters.time_min.toString());
 | 
				
			||||||
 | 
					      if (filters.time_max)
 | 
				
			||||||
 | 
					        filtersS.append("time_max", filters.time_max.toString());
 | 
				
			||||||
 | 
					      if (filters.label) filtersS.append("label", filters.label);
 | 
				
			||||||
 | 
					      if (filters.limit) filtersS.append("limit", filters.limit);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      await APIClient.exec({
 | 
					      await APIClient.exec({
 | 
				
			||||||
        uri: `/account/${account_id}/movements`,
 | 
					        uri: `/account/${account_id}/movements?${filtersS}`,
 | 
				
			||||||
        method: "GET",
 | 
					        method: "GET",
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    ).data;
 | 
					    ).data;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ import { AmountWidget } from "../widgets/AmountWidget";
 | 
				
			|||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
import { DateWidget } from "../widgets/DateWidget";
 | 
					import { DateWidget } from "../widgets/DateWidget";
 | 
				
			||||||
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
 | 
					import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
 | 
				
			||||||
import { MovementWidget } from "../widgets/MovementWidget";
 | 
					import { AsyncMovementWidget } from "../widgets/MovementWidget";
 | 
				
			||||||
import { NewMovementWidget } from "../widgets/NewMovementWidget";
 | 
					import { NewMovementWidget } from "../widgets/NewMovementWidget";
 | 
				
			||||||
import { UploadedFileWidget } from "../widgets/UploadedFileWidget";
 | 
					import { UploadedFileWidget } from "../widgets/UploadedFileWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -271,7 +271,7 @@ function InboxTable(p: {
 | 
				
			|||||||
      flex: 3,
 | 
					      flex: 3,
 | 
				
			||||||
      renderCell: (params) => {
 | 
					      renderCell: (params) => {
 | 
				
			||||||
        if (params.row.movement_id)
 | 
					        if (params.row.movement_id)
 | 
				
			||||||
          return <MovementWidget id={params.row.movement_id} />;
 | 
					          return <AsyncMovementWidget id={params.row.movement_id} />;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,13 +3,12 @@ import CallReceivedIcon from "@mui/icons-material/CallReceived";
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { Movement, MovementApi } from "../api/MovementsApi";
 | 
					import { Movement, MovementApi } from "../api/MovementsApi";
 | 
				
			||||||
import { useAccounts } from "../hooks/AccountsListProvider";
 | 
					import { useAccounts } from "../hooks/AccountsListProvider";
 | 
				
			||||||
 | 
					import { fmtDateFromTime } from "../utils/DateUtils";
 | 
				
			||||||
import { AccountIconWidget } from "./AccountIconWidget";
 | 
					import { AccountIconWidget } from "./AccountIconWidget";
 | 
				
			||||||
import { AmountWidget } from "./AmountWidget";
 | 
					import { AmountWidget } from "./AmountWidget";
 | 
				
			||||||
import { AsyncWidget } from "./AsyncWidget";
 | 
					import { AsyncWidget } from "./AsyncWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function MovementWidget(p: { id: number }): React.ReactElement {
 | 
					export function AsyncMovementWidget(p: { id: number }): React.ReactElement {
 | 
				
			||||||
  const accounts = useAccounts();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [movement, setMovement] = React.useState<Movement | undefined>();
 | 
					  const [movement, setMovement] = React.useState<Movement | undefined>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const load = async () => {
 | 
					  const load = async () => {
 | 
				
			||||||
@@ -21,7 +20,15 @@ export function MovementWidget(p: { id: number }): React.ReactElement {
 | 
				
			|||||||
      loadKey={p.id}
 | 
					      loadKey={p.id}
 | 
				
			||||||
      load={load}
 | 
					      load={load}
 | 
				
			||||||
      errMsg="Failed to load movement!"
 | 
					      errMsg="Failed to load movement!"
 | 
				
			||||||
      build={() => (
 | 
					      build={() => <MovementWidget movement={movement!} />}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function MovementWidget(p: { movement: Movement }): React.ReactElement {
 | 
				
			||||||
 | 
					  const accounts = useAccounts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
    <span
 | 
					    <span
 | 
				
			||||||
      style={{
 | 
					      style={{
 | 
				
			||||||
        display: "inline-flex",
 | 
					        display: "inline-flex",
 | 
				
			||||||
@@ -29,7 +36,7 @@ export function MovementWidget(p: { id: number }): React.ReactElement {
 | 
				
			|||||||
        height: "100%",
 | 
					        height: "100%",
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
          {movement!.amount > 0 ? (
 | 
					      {p.movement!.amount > 0 ? (
 | 
				
			||||||
        <CallReceivedIcon color="success" />
 | 
					        <CallReceivedIcon color="success" />
 | 
				
			||||||
      ) : (
 | 
					      ) : (
 | 
				
			||||||
        <CallMadeIcon color="error" />
 | 
					        <CallMadeIcon color="error" />
 | 
				
			||||||
@@ -44,23 +51,20 @@ export function MovementWidget(p: { id: number }): React.ReactElement {
 | 
				
			|||||||
        }}
 | 
					        }}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <span style={{ height: "1em", lineHeight: 1 }}>
 | 
					        <span style={{ height: "1em", lineHeight: 1 }}>
 | 
				
			||||||
              {movement?.label}
 | 
					          {p.movement?.label}
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
            <span
 | 
					        <span style={{ display: "flex", alignItems: "center", lineHeight: 1 }}>
 | 
				
			||||||
              style={{ display: "flex", alignItems: "center", lineHeight: 1 }}
 | 
					          <AmountWidget amount={p.movement!.amount} />
 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <AmountWidget amount={movement!.amount} />
 | 
					 | 
				
			||||||
          <span style={{ width: "0.5em" }} />
 | 
					          <span style={{ width: "0.5em" }} />
 | 
				
			||||||
          •
 | 
					          •
 | 
				
			||||||
          <span style={{ width: "0.5em" }} />
 | 
					          <span style={{ width: "0.5em" }} />
 | 
				
			||||||
              <AccountIconWidget
 | 
					          <AccountIconWidget account={accounts.get(p.movement!.account_id)!} />
 | 
				
			||||||
                account={accounts.get(movement!.account_id)!}
 | 
					          {accounts.get(p.movement!.account_id)?.name}
 | 
				
			||||||
              />
 | 
					          <span style={{ width: "0.5em" }} />
 | 
				
			||||||
              {accounts.get(movement!.account_id)?.name}
 | 
					          • <span style={{ width: "0.5em" }} />
 | 
				
			||||||
 | 
					          {fmtDateFromTime(p.movement.time)}
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
      </span>
 | 
					      </span>
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,7 +113,11 @@ export function NewMovementWidget(
 | 
				
			|||||||
            >
 | 
					            >
 | 
				
			||||||
              <UploadedFileWidget file_id={file.id} />
 | 
					              <UploadedFileWidget file_id={file.id} />
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
            <IconButton onClick={() => { setFile(undefined); }}>
 | 
					            <IconButton
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                setFile(undefined);
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
              <ClearIcon />
 | 
					              <ClearIcon />
 | 
				
			||||||
            </IconButton>
 | 
					            </IconButton>
 | 
				
			||||||
          </>
 | 
					          </>
 | 
				
			||||||
@@ -156,7 +160,6 @@ export function NewMovementWidget(
 | 
				
			|||||||
      {/* Amount input */}
 | 
					      {/* Amount input */}
 | 
				
			||||||
      <AmountInput
 | 
					      <AmountInput
 | 
				
			||||||
        editable
 | 
					        editable
 | 
				
			||||||
        type="text"
 | 
					 | 
				
			||||||
        placeholder="Amount"
 | 
					        placeholder="Amount"
 | 
				
			||||||
        style={{ flex: 1, maxWidth: "110px" }}
 | 
					        style={{ flex: 1, maxWidth: "110px" }}
 | 
				
			||||||
        value={amount ?? 0}
 | 
					        value={amount ?? 0}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										127
									
								
								moneymgr_web/src/widgets/SelectMovementWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								moneymgr_web/src/widgets/SelectMovementWidget.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					import { ListItem, ListItemButton, Paper, Typography } from "@mui/material";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { AccountInput } from "./forms/AccountInput";
 | 
				
			||||||
 | 
					import { AmountInput } from "./forms/AmountInput";
 | 
				
			||||||
 | 
					import { DateInput } from "./forms/DateInput";
 | 
				
			||||||
 | 
					import { TextInput } from "./forms/TextInput";
 | 
				
			||||||
 | 
					import { Movement, MovementApi } from "../api/MovementsApi";
 | 
				
			||||||
 | 
					import { AsyncWidget } from "./AsyncWidget";
 | 
				
			||||||
 | 
					import { AsyncMovementWidget, MovementWidget } from "./MovementWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function SelectMovementWidget(p: {
 | 
				
			||||||
 | 
					  value?: number;
 | 
				
			||||||
 | 
					  onChange: (movementId: number) => void;
 | 
				
			||||||
 | 
					  initialValues?: {
 | 
				
			||||||
 | 
					    amount?: number;
 | 
				
			||||||
 | 
					    accountId?: number;
 | 
				
			||||||
 | 
					    date?: number;
 | 
				
			||||||
 | 
					    label?: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const [amount, setAmount] = React.useState<number | undefined>(
 | 
				
			||||||
 | 
					    p.initialValues?.amount
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [accountId, setAccountId] = React.useState<number | undefined>(
 | 
				
			||||||
 | 
					    p.initialValues?.accountId
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [date, setDate] = React.useState<number | undefined>(
 | 
				
			||||||
 | 
					    p.initialValues?.date
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [label, setLabel] = React.useState<string | undefined>(
 | 
				
			||||||
 | 
					    p.initialValues?.label
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filters = {
 | 
				
			||||||
 | 
					    label: label,
 | 
				
			||||||
 | 
					    amount_min: amount ? amount - 0.5 : undefined,
 | 
				
			||||||
 | 
					    amount_max: amount ? amount + 0.5 : undefined,
 | 
				
			||||||
 | 
					    time_min: date ? date - 3600 * 24 : undefined,
 | 
				
			||||||
 | 
					    time_max: date ? date + 3600 * 24 : undefined,
 | 
				
			||||||
 | 
					    limit: 10,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [list, setList] = React.useState<Movement[] | undefined>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const load = async () => {
 | 
				
			||||||
 | 
					    if (accountId)
 | 
				
			||||||
 | 
					      setList(await MovementApi.GetAccountMovements(accountId, filters));
 | 
				
			||||||
 | 
					    else setList(undefined);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Paper style={{ padding: "10px" }}>
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <AccountInput
 | 
				
			||||||
 | 
					          value={accountId}
 | 
				
			||||||
 | 
					          onChange={setAccountId}
 | 
				
			||||||
 | 
					          style={{ flex: 20 }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <span style={{ flex: 1 }} />
 | 
				
			||||||
 | 
					        <AmountInput
 | 
				
			||||||
 | 
					          editable
 | 
				
			||||||
 | 
					          value={amount ?? 0}
 | 
				
			||||||
 | 
					          onValueChange={setAmount}
 | 
				
			||||||
 | 
					          label="Amount"
 | 
				
			||||||
 | 
					          placeholder="Amount"
 | 
				
			||||||
 | 
					          variant="outlined"
 | 
				
			||||||
 | 
					          style={{ height: "100%", flex: 20 }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <span style={{ flex: 1 }} />
 | 
				
			||||||
 | 
					        <DateInput
 | 
				
			||||||
 | 
					          editable
 | 
				
			||||||
 | 
					          value={date}
 | 
				
			||||||
 | 
					          onValueChange={setDate}
 | 
				
			||||||
 | 
					          label="Date"
 | 
				
			||||||
 | 
					          style={{ flex: 20 }}
 | 
				
			||||||
 | 
					          variant="outlined"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <span style={{ flex: 1 }} />
 | 
				
			||||||
 | 
					        <TextInput
 | 
				
			||||||
 | 
					          editable
 | 
				
			||||||
 | 
					          value={label}
 | 
				
			||||||
 | 
					          onValueChange={setLabel}
 | 
				
			||||||
 | 
					          label="Label"
 | 
				
			||||||
 | 
					          variant="outlined"
 | 
				
			||||||
 | 
					          style={{ flex: 20 }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <AsyncWidget
 | 
				
			||||||
 | 
					        loadKey={accountId + "/" + JSON.stringify(filters)}
 | 
				
			||||||
 | 
					        load={load}
 | 
				
			||||||
 | 
					        errMsg="Failed to load the list of movements!"
 | 
				
			||||||
 | 
					        build={() => {
 | 
				
			||||||
 | 
					          if (list === null)
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					              <Typography style={{ textAlign: "center", padding: "20px" }}>
 | 
				
			||||||
 | 
					                Select an account to begin research.
 | 
				
			||||||
 | 
					              </Typography>
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          if (list?.length === 0)
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					              <Typography style={{ textAlign: "center", padding: "20px" }}>
 | 
				
			||||||
 | 
					                No result.
 | 
				
			||||||
 | 
					              </Typography>
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return (
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
 | 
					              {list?.map((entry) => (
 | 
				
			||||||
 | 
					                <ListItem>
 | 
				
			||||||
 | 
					                  <ListItemButton
 | 
				
			||||||
 | 
					                    selected={entry.id === p.value}
 | 
				
			||||||
 | 
					                    onClick={() => p.onChange(entry.id)}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <MovementWidget movement={entry} />
 | 
				
			||||||
 | 
					                  </ListItemButton>
 | 
				
			||||||
 | 
					                </ListItem>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </Paper>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,21 +4,25 @@ import { useAccounts } from "../../hooks/AccountsListProvider";
 | 
				
			|||||||
import { AmountWidget } from "../AmountWidget";
 | 
					import { AmountWidget } from "../AmountWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function AccountInput(p: {
 | 
					export function AccountInput(p: {
 | 
				
			||||||
  value: number;
 | 
					  value?: number;
 | 
				
			||||||
  onChange: (value: number) => void;
 | 
					  onChange: (value: number) => void;
 | 
				
			||||||
  label?: string;
 | 
					  label?: string;
 | 
				
			||||||
 | 
					  style?: React.CSSProperties;
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  const accounts = useAccounts();
 | 
					  const accounts = useAccounts();
 | 
				
			||||||
  let current = p.value;
 | 
					  let current = p.value;
 | 
				
			||||||
  if (!current && accounts.list.list.length > 0)
 | 
					  if (!current && accounts.list.list.length > 0) {
 | 
				
			||||||
    current = (accounts.list.default ?? accounts.list.list[0]).id;
 | 
					    current = (accounts.list.default ?? accounts.list.list[0]).id;
 | 
				
			||||||
 | 
					    p.onChange(current);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Select
 | 
					    <Select
 | 
				
			||||||
      value={p.value}
 | 
					      value={current}
 | 
				
			||||||
      label={p.label ?? ""}
 | 
					      label={p.label ?? ""}
 | 
				
			||||||
      onChange={(e) => p.onChange(Number(e.target.value))}
 | 
					      onChange={(e) => p.onChange(Number(e.target.value))}
 | 
				
			||||||
      size="small"
 | 
					      size="small"
 | 
				
			||||||
 | 
					      style={p.style}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      {accounts.list.list.map((a) => (
 | 
					      {accounts.list.list.map((a) => (
 | 
				
			||||||
        <MenuItem value={a.id}>
 | 
					        <MenuItem value={a.id}>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { TextInput } from "./TextInput";
 | 
					import { TextInput } from "./TextInput";
 | 
				
			||||||
 | 
					import { TextFieldVariants } from "@mui/material";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum InputState {
 | 
					enum InputState {
 | 
				
			||||||
  Normal,
 | 
					  Normal,
 | 
				
			||||||
@@ -8,12 +9,13 @@ enum InputState {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function AmountInput(p: {
 | 
					export function AmountInput(p: {
 | 
				
			||||||
 | 
					  label?: string;
 | 
				
			||||||
  editable: boolean;
 | 
					  editable: boolean;
 | 
				
			||||||
  type: string;
 | 
					 | 
				
			||||||
  placeholder: string;
 | 
					  placeholder: string;
 | 
				
			||||||
  style: React.CSSProperties;
 | 
					  style?: React.CSSProperties;
 | 
				
			||||||
  value: number;
 | 
					  value: number;
 | 
				
			||||||
  onValueChange: (val: number) => void;
 | 
					  onValueChange: (val: number) => void;
 | 
				
			||||||
 | 
					  variant?: TextFieldVariants;
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  const [state, setState] = React.useState(InputState.Normal);
 | 
					  const [state, setState] = React.useState(InputState.Normal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import { DatePicker } from "@mui/x-date-pickers";
 | 
					import { DatePicker } from "@mui/x-date-pickers";
 | 
				
			||||||
import { dateToTime, timeToDate } from "../../utils/DateUtils";
 | 
					import { dateToTime, timeToDate } from "../../utils/DateUtils";
 | 
				
			||||||
 | 
					import { TextFieldVariants } from "@mui/material";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DateInput(p: {
 | 
					export function DateInput(p: {
 | 
				
			||||||
  ref?: React.Ref<HTMLInputElement>;
 | 
					  ref?: React.Ref<HTMLInputElement>;
 | 
				
			||||||
@@ -9,6 +10,7 @@ export function DateInput(p: {
 | 
				
			|||||||
  style?: React.CSSProperties;
 | 
					  style?: React.CSSProperties;
 | 
				
			||||||
  value: number | undefined;
 | 
					  value: number | undefined;
 | 
				
			||||||
  onValueChange: (v: number | undefined) => void;
 | 
					  onValueChange: (v: number | undefined) => void;
 | 
				
			||||||
 | 
					  variant?: TextFieldVariants;
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <DatePicker
 | 
					    <DatePicker
 | 
				
			||||||
@@ -17,7 +19,7 @@ export function DateInput(p: {
 | 
				
			|||||||
      label={p.label}
 | 
					      label={p.label}
 | 
				
			||||||
      slotProps={{
 | 
					      slotProps={{
 | 
				
			||||||
        field: { ref: p.ref },
 | 
					        field: { ref: p.ref },
 | 
				
			||||||
        textField: { variant: "standard", style: p.style },
 | 
					        textField: { variant: p.variant ?? "standard", style: p.style },
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      value={timeToDate(p.value)}
 | 
					      value={timeToDate(p.value)}
 | 
				
			||||||
      onChange={(v) => {
 | 
					      onChange={(v) => {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user