Skip to content

Commit

Permalink
feat(web): initial pass at making lists mobile optimized
Browse files Browse the repository at this point in the history
  • Loading branch information
mikonse committed Dec 29, 2024
1 parent 9f366ec commit 77897a7
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,36 +48,17 @@ import { useConfig } from "@/core/config";
const drawerWidth = 240;
const AUTH_FALLBACK = "/login";

export const AuthenticatedLayout: React.FC = () => {
type DrawerContentProps = {
groupId: number | undefined;
};

const DrawerContent: React.FC<DrawerContentProps> = ({ groupId }) => {
const theme = useTheme();
const cfg = useConfig();
const { t } = useTranslation();
const authenticated = useAppSelector(selectIsAuthenticated);
const location = useLocation();
const params = useParams();
const groupId = params["groupId"] ? Number(params["groupId"]) : undefined;
const [anchorEl, setAnchorEl] = React.useState<Element | null>(null);
const theme: Theme = useTheme();
const dotsMenuOpen = Boolean(anchorEl);
const cfg = useConfig();
const isLargeScreen = useMediaQuery(theme.breakpoints.up("sm"));

const [mobileOpen, setMobileOpen] = React.useState(true);
if (!authenticated) {
return <Navigate to={`${AUTH_FALLBACK}?next=${location.pathname}`} />;
}

const handleProfileMenuOpen = (event: React.MouseEvent) => {
setAnchorEl(event.currentTarget);
};

const handleDotsMenuClose = () => {
setAnchorEl(null);
};

const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};

const drawer = (
return (
<div className={styles["sidebarContainer"]}>
<div className={styles["sidebarScrollContainer"]}>
{groupId !== undefined && (
Expand Down Expand Up @@ -172,6 +153,35 @@ export const AuthenticatedLayout: React.FC = () => {
</div>
</div>
);
};

export const AuthenticatedLayout: React.FC = () => {
const { t } = useTranslation();
const authenticated = useAppSelector(selectIsAuthenticated);
const location = useLocation();
const params = useParams();
const groupId = params["groupId"] ? Number(params["groupId"]) : undefined;
const [anchorEl, setAnchorEl] = React.useState<Element | null>(null);
const theme: Theme = useTheme();
const dotsMenuOpen = Boolean(anchorEl);
const isLargeScreen = useMediaQuery(theme.breakpoints.up("sm"));

const [mobileOpen, setMobileOpen] = React.useState(true);
if (!authenticated) {
return <Navigate to={`${AUTH_FALLBACK}?next=${location.pathname}`} />;
}

const handleProfileMenuOpen = (event: React.MouseEvent) => {
setAnchorEl(event.currentTarget);
};

const handleDotsMenuClose = () => {
setAnchorEl(null);
};

const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};

return (
<Box sx={{ display: "flex" }}>
Expand Down Expand Up @@ -267,7 +277,7 @@ export const AuthenticatedLayout: React.FC = () => {
overflowY: "hidden",
}}
>
{drawer}
<DrawerContent groupId={groupId} />
</Drawer>
</Box>
<Box
Expand Down
11 changes: 5 additions & 6 deletions frontend/apps/web/src/components/ShareSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { useIsSmallScreen } from "@/hooks";
import { NumericInput } from "@abrechnung/components";
import { getAccountSortFunc } from "@abrechnung/core";
import { useGroupAccounts } from "@abrechnung/redux";
import { Account, TransactionShare } from "@abrechnung/types";
import { Clear as ClearIcon, Search as SearchIcon } from "@mui/icons-material";
Expand All @@ -17,18 +20,14 @@ import {
TableHead,
TableRow,
TextField,
Theme,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import { getAccountLink } from "../utils";
import { NumericInput } from "@abrechnung/components";
import { getAccountIcon } from "./style/AbrechnungIcons";
import { getAccountSortFunc } from "@abrechnung/core";
import { useTranslation } from "react-i18next";

interface RowProps {
account: Account;
Expand Down Expand Up @@ -129,7 +128,7 @@ export const ShareSelect: React.FC<ShareSelectProps> = ({
}) => {
const { t } = useTranslation();
const theme = useTheme();
const isSmallScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down("sm"));
const isSmallScreen = useIsSmallScreen();

const [showEvents, setShowEvents] = React.useState(false);
const [showAdvanced, setShowAdvanced] = React.useState(false);
Expand Down
1 change: 1 addition & 0 deletions frontend/apps/web/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./useFormatCurrency";
export * from "./useIsSmallScreen";
26 changes: 16 additions & 10 deletions frontend/apps/web/src/hooks/useFormatCurrency.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as React from "react";

const formatDef = new Intl.NumberFormat("de", {
currency: "EUR",
style: "currency",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
import { useTranslation } from "react-i18next";

export const useFormatCurrency = () => {
return React.useCallback((value: number, currencySymbol: string) => {
return formatDef.format(value).replace("€", currencySymbol);
}, []);
const { i18n } = useTranslation();

return React.useCallback(
(value: number, currencySymbol: string) => {
const formatDef = new Intl.NumberFormat(i18n.language, {
currency: "EUR",
style: "currency",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});

return formatDef.format(value).replace("€", currencySymbol);
},
[i18n]
);
};
6 changes: 6 additions & 0 deletions frontend/apps/web/src/hooks/useIsSmallScreen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useMediaQuery, useTheme } from "@mui/material";

export const useIsSmallScreen = () => {
const theme = useTheme();
return useMediaQuery(theme.breakpoints.down("md"));
};
10 changes: 5 additions & 5 deletions frontend/apps/web/src/pages/accounts/BalanceBarGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as React from "react";
import { useFormatCurrency } from "@/hooks";
import { useFormatCurrency, useIsSmallScreen } from "@/hooks";
import { useAppSelector } from "@/store";
import { Group } from "@abrechnung/api";
import { selectAccountBalances, useSortedAccounts } from "@abrechnung/redux";
import { Theme, useMediaQuery } from "@mui/material";
import { Theme } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import * as React from "react";
import { useNavigate } from "react-router";
import { Bar, BarChart, Cell, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
import { CategoricalChartFunc } from "recharts/types/chart/generateCategoricalChart";
import { Group } from "@abrechnung/api";

export type BalanceBarGraphProps = {
group: Group;
Expand All @@ -24,7 +24,7 @@ type Data = {
export const BalanceBarGraph: React.FC<BalanceBarGraphProps> = ({ group }) => {
const formatCurrency = useFormatCurrency();
const theme: Theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
const isSmallScreen = useIsSmallScreen();
const navigate = useNavigate();

const personalAccounts = useSortedAccounts(group.id, "name", "personal");
Expand Down
22 changes: 5 additions & 17 deletions frontend/apps/web/src/pages/accounts/Balances.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { GroupArchivedDisclaimer } from "@/components";
import { BalanceTable } from "@/components/accounts/BalanceTable";
import { MobilePaper, ListItemLink } from "@/components/style";
import { ListItemLink, MobilePaper } from "@/components/style";
import { useTitle } from "@/core/utils";
import { useFormatCurrency } from "@/hooks";
import { useFormatCurrency, useIsSmallScreen } from "@/hooks";
import { useAppSelector } from "@/store";
import {
selectAccountBalances,
Expand All @@ -11,25 +12,12 @@ import {
useSortedAccounts,
} from "@abrechnung/redux";
import { TabContext, TabList, TabPanel } from "@mui/lab";
import {
Alert,
AlertTitle,
Box,
Button,
Divider,
List,
ListItemText,
Tab,
Theme,
Typography,
useMediaQuery,
} from "@mui/material";
import { Alert, AlertTitle, Box, Button, Divider, List, ListItemText, Tab, Theme, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Navigate, Link as RouterLink } from "react-router";
import { BalanceBarGraph } from "./BalanceBarGraph";
import { GroupArchivedDisclaimer } from "@/components";

interface Props {
groupId: number;
Expand All @@ -39,7 +27,7 @@ export const Balances: React.FC<Props> = ({ groupId }) => {
const { t } = useTranslation();
const formatCurrency = useFormatCurrency();
const theme: Theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
const isSmallScreen = useIsSmallScreen();

const group = useGroup(groupId);
const personalAccounts = useSortedAccounts(groupId, "name", "personal");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { GroupArchivedDisclaimer } from "@/components";
import { TagSelector } from "@/components/TagSelector";
import { DeleteAccountModal } from "@/components/accounts/DeleteAccountModal";
import { MobilePaper } from "@/components/style";
import { useTitle } from "@/core/utils";
import { useIsSmallScreen } from "@/hooks";
import { useAppDispatch } from "@/store";
import { getAccountLink } from "@/utils";
import { AccountSortMode } from "@abrechnung/core";
import { createAccount, useGroup, useIsGroupWritable, useSortedAccounts } from "@abrechnung/redux";
import { Account } from "@abrechnung/types";
import { Add as AddIcon, Clear as ClearIcon, Search as SearchIcon } from "@mui/icons-material";
import {
Alert,
Expand All @@ -16,23 +25,12 @@ import {
Pagination,
Select,
Stack,
Theme,
Tooltip,
useMediaQuery,
useTheme,
} from "@mui/material";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Navigate, useNavigate } from "react-router";
import { TagSelector } from "@/components/TagSelector";
import { DeleteAccountModal } from "@/components/accounts/DeleteAccountModal";
import { MobilePaper } from "@/components/style";
import { useTitle } from "@/core/utils";
import { useAppDispatch } from "@/store";
import { getAccountLink } from "@/utils";
import { ClearingAccountListItem } from "./ClearingAccountListItem";
import { useTranslation } from "react-i18next";
import { Account } from "@abrechnung/types";
import { GroupArchivedDisclaimer } from "@/components";

interface Props {
groupId: number;
Expand All @@ -47,8 +45,7 @@ export const ClearingAccountList: React.FC<Props> = ({ groupId }) => {
const navigate = useNavigate();
const group = useGroup(groupId);
const isGroupWritable = useIsGroupWritable(groupId);
const theme: Theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("md"));
const isSmallScreen = useIsSmallScreen();

const [searchValue, setSearchValue] = useState("");
const [tagFilter, setTagFilter] = useState<string[]>(emptyList);
Expand Down Expand Up @@ -92,27 +89,24 @@ export const ClearingAccountList: React.FC<Props> = ({ groupId }) => {
<MobilePaper>
<Stack spacing={1}>
<GroupArchivedDisclaimer group={group} />
<Box
sx={{
display: "flex",
flexDirection: { xs: "column", sm: "column", md: "row", lg: "row" },
alignItems: { md: "flex-end" },
pl: "16px",
justifyContent: "space-between",
}}
<Stack
direction={{ sm: "column", md: "row" }}
alignItems={{ md: "flex-end" }}
justifyContent="space-between"
>
<Box sx={{ display: "flex-item" }}>
<Box sx={{ minWidth: "56px", pt: "16px" }}>
<SearchIcon sx={{ color: "action.active" }} />
</Box>
<Stack direction={{ sm: "column", md: "row" }} justifyContent="space-between" spacing={1}>
<Input
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
placeholder={t("common.search")}
inputProps={{
"aria-label": "search",
}}
sx={{ pt: "16px" }}
startAdornment={
<InputAdornment position="start">
<SearchIcon sx={{ color: "action.active" }} />
</InputAdornment>
}
endAdornment={
<InputAdornment position="end">
<IconButton
Expand All @@ -125,7 +119,7 @@ export const ClearingAccountList: React.FC<Props> = ({ groupId }) => {
</InputAdornment>
}
/>
<FormControl variant="standard" sx={{ minWidth: 120, ml: 3 }}>
<FormControl variant="standard" sx={{ minWidth: 120 }}>
<InputLabel id="select-sort-by-label">{t("common.sortBy")}</InputLabel>
<Select
labelId="select-sort-by-label"
Expand All @@ -140,7 +134,7 @@ export const ClearingAccountList: React.FC<Props> = ({ groupId }) => {
<MenuItem value="dateInfo">{t("common.date")}</MenuItem>
</Select>
</FormControl>
<FormControl variant="standard" sx={{ minWidth: 120, ml: 3 }}>
<FormControl variant="standard" sx={{ minWidth: 120 }}>
<TagSelector
label={t("common.filterByTags")}
groupId={groupId}
Expand All @@ -151,7 +145,7 @@ export const ClearingAccountList: React.FC<Props> = ({ groupId }) => {
chipProps={{ size: "small" }}
/>
</FormControl>
</Box>
</Stack>
{!isSmallScreen && isGroupWritable && (
<Box sx={{ display: "flex-item" }}>
<Tooltip title={t("events.createEvent")}>
Expand All @@ -161,7 +155,7 @@ export const ClearingAccountList: React.FC<Props> = ({ groupId }) => {
</Tooltip>
</Box>
)}
</Box>
</Stack>
<Divider />
<List>
{paginatedAccounts.length === 0 ? (
Expand Down
Loading

0 comments on commit 77897a7

Please sign in to comment.