Skip to content

Commit

Permalink
Allow grouping bookmark color to be selected
Browse files Browse the repository at this point in the history
  • Loading branch information
thaapasa committed Feb 28, 2024
1 parent 4df5847 commit 946561a
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 46 deletions.
13 changes: 13 additions & 0 deletions migrations/20240228185533_add_grouping_color.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';

/* eslint-disable no-undef */

exports.up = knex =>
knex.raw(/*sql*/ `
ALTER TABLE expense_groupings ADD COLUMN color TEXT;
`);

exports.down = knex =>
knex.raw(/*sql*/ `
ALTER TABLE expense_groupings DROP COLUMN color;
`);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"ps-server": "ps -ef | grep BookkeeperServer | grep -v grep | awk '{ print $2; }'",
"migrate": "knex migrate:latest",
"migrate-make": "knex migrate:make",
"migrate-rollback": "knex migrate:rollback",
"rollback": "knex migrate:rollback",
"seed": "knex seed:run",
"example-data": "bun run src/tools/addExampleData.ts",
"find-cyclic": "bunx madge --extensions ts,tsx --circular ."
Expand Down
4 changes: 4 additions & 0 deletions src/client/ui/component/BasicElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export const FlexRow = styled('div')`
display: flex;
flex-direction: row;
box-sizing: border-box;
&.vcenter {
align-items: center;
}
`;

export const FlexColumn = styled('div')`
Expand Down
84 changes: 84 additions & 0 deletions src/client/ui/component/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import styled from '@emotion/styled';
import { colors, IconButton } from '@mui/material';
import * as React from 'react';

import { typedKeys } from 'shared/util';

import { colorScheme } from '../Colors';
import { useToggle } from '../hooks/useToggle';
import { Icons } from '../icons/Icons';
import { FlexColumn, FlexRow } from './BasicElements';
import { TextEdit } from './TextEdit';

interface ColorPickerProps {
value: string;
onChange: (color: string) => void;
}

const allColors = typedKeys(colors);
const colorPalette = typedKeys(colors.amber);

const defaultPaletteIdx = 4;

export const ColorPicker: React.FC<ColorPickerProps> = ({ value, onChange }) => {
const [open, toggle] = useToggle();
const [palette, setPalette] = React.useState<number>(defaultPaletteIdx);
return (
<>
<FlexRow className="vcenter">
<ColorBall color={value} onClick={toggle} title={value} className="example" />
{open ? <TextEdit value={value} onChange={onChange} /> : value}
</FlexRow>
{open ? (
<FlexRow className="vcenter">
<FlexColumn>
<IconButton
size="small"
onClick={() => setPalette(palette > 0 ? palette - 1 : palette)}
>
<Icons.SortUp fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => setPalette(palette < colorPalette.length - 1 ? palette + 1 : palette)}
>
<Icons.SortDown fontSize="small" />
</IconButton>
</FlexColumn>
<ColorOptions>
{allColors.map(c => {
const col = (colors as any)[c][colorPalette[palette]];
return <ColorBall color={col} key={c} onClick={() => onChange(col)} />;
})}
</ColorOptions>
</FlexRow>
) : null}
</>
);
};

const ColorBall = styled('div')`
display: inline-flex;
border: 1px solid ${colorScheme.gray.standard};
border-radius: 50%;
width: 24px;
height: 24px;
&.example {
width: 32px;
height: 32px;
margin-right: 8px;
}
${({ color }: { color: string }) => `
background-color: ${color};
`};
`;

const ColorOptions = styled('div')`
display: inline-grid;
flex: 1;
grid-template-columns: repeat(auto-fill, 28px);
row-gap: 4px;
column-gap: 4px;
margin: 16px 0;
`;
13 changes: 5 additions & 8 deletions src/client/ui/expense/row/ExpenseRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ActivatableTextField } from 'client/ui/component/ActivatableTextField';
import { ExpanderIcon } from 'client/ui/component/ExpanderIcon';
import { UserAvatar } from 'client/ui/component/UserAvatar';
import { UserPrompts } from 'client/ui/dialog/DialogState';
import { GroupedExpenseIcon } from 'client/ui/grouping/GroupedExpenseIcon';
import { ExpenseTypeIcon } from 'client/ui/icons/ExpenseType';
import { ToolIcon } from 'client/ui/icons/ToolIcon';
import { Flex, media, VCenterRow } from 'client/ui/Styles';
Expand All @@ -34,7 +35,6 @@ import {
BalanceColumn,
CategoryColumn,
DateColumn,
GroupedExpenseIcon,
IconToolArea,
NameColumn,
ReceiverColumn,
Expand Down Expand Up @@ -203,6 +203,7 @@ export class ExpenseRowImpl extends React.Component<ExpenseRowProps, ExpenseRowS
style.background = colors.income;
}
const firstDay = !this.props.prev || !toDayjs(expense.date).isSame(this.props.prev.date, 'day');
const grouping = this.props.groupingMap[this.props.expense?.groupingId ?? 0];
return (
<>
<Row className={firstDay && this.props.dateBorder ? 'first-day' : ''}>
Expand Down Expand Up @@ -233,16 +234,12 @@ export class ExpenseRowImpl extends React.Component<ExpenseRowProps, ExpenseRowS
onClick={() => this.props.addFilter(ExpenseFilters.unconfirmed, 'Alustavat')}
/>
)}
{this.props.expense.groupingId ? (
{grouping ? (
<GroupedExpenseIcon
grouping={grouping}
onClick={() =>
this.props.addFilter(
e => e.groupingId === this.props.expense.groupingId,
this.props.groupingMap[this.props.expense.groupingId!]?.title ?? 'Ryhmitelty',
)
this.props.addFilter(e => e.groupingId === grouping.id, grouping.title)
}
title={this.props.groupingMap[this.props.expense.groupingId]?.title}
image={this.props.groupingMap[this.props.expense.groupingId]?.image}
/>
) : null}
</IconToolArea>
Expand Down
30 changes: 1 addition & 29 deletions src/client/ui/expense/row/ExpenseTableLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { CircularProgress, colors, styled } from '@mui/material';
import { CircularProgress, styled } from '@mui/material';
import * as React from 'react';

import { windowSizeP } from 'client/data/State';
import { colorScheme, primaryColors } from 'client/ui/Colors';
import { connect } from 'client/ui/component/BaconConnect';
import { Bookmark } from 'client/ui/icons/Bookmark';
import { Icons } from 'client/ui/icons/Icons';
import { QuestionBookmark } from 'client/ui/icons/QuestionBookmark';
import { getScreenSizeClassName, media, ScreenSizeClassName } from 'client/ui/Styles';
Expand Down Expand Up @@ -241,39 +240,12 @@ export const UnconfirmedIcon: React.FC<UnconfimedIconProps> = ({ size, title, on
</IconContainer>
);

type GroupedExpenseIconProps = {
size?: number;
title: string;
onClick?: () => void;
image?: string;
};
export const GroupedExpenseIcon: React.FC<GroupedExpenseIconProps> = ({ size, title, onClick }) => (
<IconContainer title={title} onClick={onClick}>
<Bookmark size={size || 24} title={title} color={colors.blue[300]} />
{title ? <GroupedExpenseIconText>{title[0]}</GroupedExpenseIconText> : null}
</IconContainer>
);

const IconContainer = styled('div')`
cursor: pointer;
position: relative;
display: inline-block;
`;

const GroupedExpenseIconText = styled('div')`
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 6px;
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-weight: bold;
color: ${colorScheme.primary.light};
`;

const RecurringExpenseSeparatorItem = styled(AllColumns)`
background-color: ${colorScheme.gray.light};
height: 24px;
Expand Down
8 changes: 8 additions & 0 deletions src/client/ui/grouping/ExpenseGroupingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { LinkButton } from '../component/NavigationBar';
import { Subtitle, TitleCss } from '../design/Text';
import { Icons } from '../icons/Icons';
import { Flex } from '../Styles';
import { GroupedExpenseIcon } from './GroupedExpenseIcon';
import { editExpenseGrouping } from './GroupingEditor';

export const ExpenseGroupingsList: React.FC<{
Expand Down Expand Up @@ -52,6 +53,7 @@ export const ExpenseGroupingView: React.FC<{
</ToolsArea>
</TitleArea>
<GroupingTotalsArea className="grouping-totals-area">
<PositionedIcon grouping={grouping} size={24} />
{grouping.image ? <GroupingImage src={grouping.image} /> : null}
<GroupingInfo>
<InfoTextArea>
Expand Down Expand Up @@ -97,6 +99,11 @@ async function deleteExpenseGrouping(grouping: ExpenseGrouping, onReload: () =>
});
}

const PositionedIcon = styled(GroupedExpenseIcon)`
position: absolute;
left: 8px;
`;

const GroupingImage = styled('img')`
width: 168px;
height: 168px;
Expand All @@ -121,6 +128,7 @@ const GroupingCard = styled(FlexColumn)`
`;

const GroupingTotalsArea = styled(FlexRow)`
position: relative;
flex: 1;
`;

Expand Down
45 changes: 45 additions & 0 deletions src/client/ui/grouping/GroupedExpenseIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import styled from '@emotion/styled';
import { colors } from '@mui/material';
import * as React from 'react';

import { ExpenseGroupingRef } from 'shared/types';

import { colorScheme } from '../Colors';
import { Bookmark } from '../icons/Bookmark';

type GroupedExpenseIconProps = {
size?: number;
grouping: ExpenseGroupingRef;
onClick?: () => void;
className?: string;
};
export const GroupedExpenseIcon: React.FC<GroupedExpenseIconProps> = ({
size,
grouping,
onClick,
className,
}) => (
<IconContainer title={grouping.title} onClick={onClick} className={className}>
<Bookmark size={size || 24} title={grouping.title} color={grouping.color ?? colors.blue[300]} />
{grouping.title ? <GroupedExpenseIconText>{grouping.title[0]}</GroupedExpenseIconText> : null}
</IconContainer>
);

const IconContainer = styled('div')`
cursor: pointer;
position: relative;
display: inline-block;
`;

const GroupedExpenseIconText = styled('div')`
position: absolute;
left: 0;
right: 0;
top: 3px;
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-weight: bold;
color: ${colorScheme.primary.light};
`;
4 changes: 4 additions & 0 deletions src/client/ui/grouping/GroupingEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { categoryMapE, getFullCategoryName } from 'client/data/Categories';

import { AsyncDataDialogContent } from '../component/AsyncDataDialog';
import { connect } from '../component/BaconConnect';
import { ColorPicker } from '../component/ColorPicker';
import { connectDialog } from '../component/DialogConnector';
import { OptionalDatePicker } from '../component/OptionalDatePicker';
import { Row } from '../component/Row';
Expand Down Expand Up @@ -88,6 +89,9 @@ const GroupingEditView: React.FC<{
<SelectionRow title="Loppupäivä">
<OptionalDatePicker value={state.endDate} onChange={state.setEndDate} />
</SelectionRow>
<SelectionRow title="Väri">
<ColorPicker value={state.color} onChange={state.setColor} />
</SelectionRow>
<SelectionRow title="Kuva">
<Row>
<ImageArea>
Expand Down
7 changes: 7 additions & 0 deletions src/client/ui/grouping/GroupingEditorState.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { colors } from '@mui/material';
import { create } from 'zustand';

import { ISODate } from 'shared/time';
Expand All @@ -13,12 +14,14 @@ export type GroupingState = {
title: string;
id: ObjectId | null;
categories: number[];
color: string;
startDate: ISODate | null;
endDate: ISODate | null;
reset(grouping: ExpenseGrouping | null): void;
setTitle(title: string): void;
setStartDate(date: ISODate | null): void;
setEndDate(date: ISODate | null): void;
setColor(color: string): void;
inputValid(): boolean;
saveGrouping(...callbacks: (() => void)[]): Promise<void>;
uploadImage(file: File, filename: string, ...callbacks: (() => void)[]): Promise<void>;
Expand All @@ -32,14 +35,17 @@ export const useGroupingState = create<GroupingState>((set, get) => ({
id: null,
startDate: null,
endDate: null,
color: colors.green[400],
categories: [],
setTitle: title => set({ title }),
setColor: color => set({ color }),
setStartDate: startDate => set({ startDate }),
setEndDate: endDate => set({ endDate }),
reset: grouping =>
set({
id: grouping?.id ?? null,
title: grouping?.title ?? '',
color: grouping?.color ?? '',
categories: grouping?.categories ?? [],
startDate: grouping?.startDate || null,
endDate: grouping?.endDate || null,
Expand All @@ -56,6 +62,7 @@ export const useGroupingState = create<GroupingState>((set, get) => ({
}
const payload: ExpenseGroupingData = {
title: s.title,
color: s.color,
categories: s.categories.length ? s.categories : [],
startDate: s.startDate ?? undefined,
endDate: s.endDate ?? undefined,
Expand Down
Loading

0 comments on commit 946561a

Please sign in to comment.