From 56afe78bc798bf4664bf0d44e930d280984cbf5a Mon Sep 17 00:00:00 2001 From: Tom Antas Date: Wed, 27 Nov 2024 19:37:25 +0100 Subject: [PATCH 1/5] feat: profit and loss - quarter and year scopes --- src/components/DatePicker/DatePicker.tsx | 8 +- .../ModeSelector/DatePickerModeSelector.tsx | 4 + .../ProfitAndLoss/ProfitAndLoss.tsx | 2 + .../ProfitAndLossChart/Indicator.tsx | 20 +- .../ProfitAndLossChart/ProfitAndLossChart.tsx | 318 ++++-------------- src/components/ProfitAndLossChart/utils.ts | 295 ++++++++++++++++ .../ProfitAndLossDatePicker.tsx | 100 ++++-- .../useProfitAndLoss/useProfitAndLoss.tsx | 19 +- .../useProfitAndLoss/useProfitAndLossLTM.tsx | 34 +- src/styles/datepicker.scss | 119 +++++-- .../AccountingOverview/AccountingOverview.tsx | 2 +- 11 files changed, 589 insertions(+), 332 deletions(-) create mode 100644 src/components/ProfitAndLossChart/utils.ts diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 50aaa7205..38036e017 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -74,7 +74,11 @@ export const DatePicker = ({ ? 'MMM, yyyy' : mode === 'timePicker' ? 'h:mm aa' - : 'MMM d, yyyy', + : mode === 'yearPicker' + ? 'yyyy' + : mode === 'quarterPicker' + ? '\'Q\'Q yyyy' + : 'MMM d, yyyy', timeIntervals = 15, timeCaption, placeholderText: _placeholderText, @@ -286,6 +290,8 @@ export const DatePicker = ({ showMonthYearPicker={ mode === 'monthPicker' || mode === 'monthRangePicker' } + showQuarterYearPicker={mode === 'quarterPicker'} + showYearPicker={mode === 'yearPicker'} dateFormat={dateFormat} renderDayContents={day => ( {day} diff --git a/src/components/DatePicker/ModeSelector/DatePickerModeSelector.tsx b/src/components/DatePicker/ModeSelector/DatePickerModeSelector.tsx index 0a9847af9..5631fffa4 100644 --- a/src/components/DatePicker/ModeSelector/DatePickerModeSelector.tsx +++ b/src/components/DatePicker/ModeSelector/DatePickerModeSelector.tsx @@ -7,6 +7,8 @@ export type RangePickerMode = | 'dayRangePicker' | 'monthRangePicker' | 'monthPicker' + | 'quarterPicker' + | 'yearPicker' export type DatePickerMode = SingularPickerMode | RangePickerMode @@ -18,6 +20,8 @@ const DATE_RANGE_MODE_CONFIG: Record = { dayRangePicker: { label: 'Select dates' }, monthPicker: { label: 'Month' }, monthRangePicker: { label: 'Select months' }, + quarterPicker: { label: 'Quarter' }, + yearPicker: { label: 'Year' }, } function toToggleOptions(allowedModes: ReadonlyArray) { diff --git a/src/components/ProfitAndLoss/ProfitAndLoss.tsx b/src/components/ProfitAndLoss/ProfitAndLoss.tsx index 08b2ae250..a90acc095 100644 --- a/src/components/ProfitAndLoss/ProfitAndLoss.tsx +++ b/src/components/ProfitAndLoss/ProfitAndLoss.tsx @@ -40,6 +40,8 @@ const PNLContext = createContext({ revenue: undefined, }, tagFilter: undefined, + period: 'month', + setPeriod: () => {}, }) type Props = PropsWithChildren & { diff --git a/src/components/ProfitAndLossChart/Indicator.tsx b/src/components/ProfitAndLossChart/Indicator.tsx index 2cfebe767..e27e72616 100644 --- a/src/components/ProfitAndLossChart/Indicator.tsx +++ b/src/components/ProfitAndLossChart/Indicator.tsx @@ -15,8 +15,11 @@ type Props = BaseProps & { setAnimateFrom: (x: number) => void customCursorSize: { width: number; height: number } setCustomCursorSize: (width: number, height: number, x: number) => void + barMargin?: number } + const emptyViewBox = { x: 0, y: 0, width: 0, height: 0 } + export const Indicator = ({ className, animateFrom, @@ -24,17 +27,18 @@ export const Indicator = ({ customCursorSize, setCustomCursorSize, viewBox = {}, + barMargin = 6, }: Props) => { const [opacityIndicator, setOpacityIndicator] = useState(0) const { x: animateTo = 0, width = 0 } = 'x' in viewBox ? viewBox : emptyViewBox - const margin = width > 12 ? 12 : 6 + const margin = barMargin const boxWidth = width + margin const xOffset = boxWidth / 2 const borderRadius = 6 const rectWidth = `${boxWidth}px` - const rectHeight = 'calc(100% - 38px)' + const rectHeight = 'calc(100% - 8px)' // useEffect callbacks run after the browser paints useEffect(() => { @@ -46,6 +50,7 @@ export const Indicator = ({ setTimeout(() => { setOpacityIndicator(1) }, 200) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [animateTo]) if (!className?.match(/selected/)) { @@ -57,15 +62,14 @@ export const Indicator = ({ const refRectWidth = ref.getBoundingClientRect().width const refRectHeight = ref.getBoundingClientRect().height if ( - customCursorSize.width !== refRectWidth || - customCursorSize.height !== refRectHeight + customCursorSize.width !== refRectWidth + || customCursorSize.height !== refRectHeight ) { - setCustomCursorSize(refRectWidth, refRectHeight, actualX - xOffset) + setCustomCursorSize(refRectWidth, refRectHeight, animateTo - xOffset) } } } - const actualX = animateFrom === -1 ? animateTo : animateFrom return ( { - const today = startOfMonth(Date.now()) - const yearAgo = sub(today, { months: 11 }) - const current = startOfMonth(new Date(currentYear, currentMonth - 1, 1)) - - if ( - differenceInMonths(startOfMonth(chartWindow.start), current) < 0 - && differenceInMonths(startOfMonth(chartWindow.end), current) > 1 - ) { - return chartWindow - } - - if (differenceInMonths(startOfMonth(chartWindow.start), current) === 0) { - return { - start: startOfMonth(sub(current, { months: 1 })), - end: endOfMonth(add(current, { months: 11 })), - } - } - - if ( - differenceInMonths(endOfMonth(chartWindow.end), endOfMonth(current)) - === 1 - && differenceInMonths(today, current) >= 1 - ) { - return { - start: startOfMonth(sub(current, { months: 10 })), - end: endOfMonth(add(current, { months: 2 })), - } - } - - if ( - differenceInMonths(current, startOfMonth(chartWindow.end)) === 0 - && differenceInMonths(current, startOfMonth(today)) > 0 - ) { - return { - start: startOfMonth(sub(current, { months: 11 })), - end: endOfMonth(add(current, { months: 1 })), - } - } - - if (current >= yearAgo) { - return { - start: startOfMonth(yearAgo), - end: endOfMonth(today), - } - } - - if (Number(current) > Number(chartWindow.end)) { - return { - start: startOfMonth(sub(current, { months: 12 })), - end: endOfMonth(current), - } - } - - if (differenceInMonths(current, startOfMonth(chartWindow.start)) < 0) { - return { - start: startOfMonth(current), - end: endOfMonth(add(current, { months: 11 })), - } - } - - return chartWindow -} - -const getLoadingValue = (data?: ProfitAndLossSummaryData[]) => { - if (!data) { - return 10000 - } - - let max = 0 - - data.forEach(x => { - const current = Math.max( - Math.abs(x.income), - Math.abs(Math.abs((x?.income || 0) - (x?.netProfit || 0))), - ) - if (current > max) { - max = current - } - }) - - return max === 0 ? 10000 : max * 0.6 -} +export type CompactView = 'xs' | 'md' | 'lg' export interface Props { forceRerenderOnDataChange?: boolean @@ -143,11 +50,16 @@ export const ProfitAndLossChart = ({ forceRerenderOnDataChange = false, tagFilter = undefined, }: Props) => { - const [compactView, setCompactView] = useState(false) - const barSize = compactView ? 10 : 20 - const { getColor, business } = useLayerContext() - const { changeDateRange, dateRange } = useContext(PNL.Context) + const { changeDateRange, dateRange, period } = useContext(PNL.Context) + const { data: linkedAccounts } = useLinkedAccounts() + + const { data, loaded, pullData } = useProfitAndLossLTM({ + currentDate: startOfMonth(Date.now()), + tagFilter: tagFilter, + }) + + const [compactView, setCompactView] = useState('lg') const [localDateRange, setLocalDateRange] = useState(dateRange) const [customCursorSize, setCustomCursorSize] = useState({ width: 0, @@ -159,6 +71,7 @@ export const ProfitAndLossChart = ({ start: startOfMonth(sub(Date.now(), { months: 11 })), end: endOfMonth(Date.now()), }) + const [animateFrom, setAnimateFrom] = useState(-1) const selectionMonth = useMemo( () => ({ @@ -168,6 +81,15 @@ export const ProfitAndLossChart = ({ [localDateRange], ) + const anyData = useMemo(() => hasAnyData(data), [data]) + + const isSyncing = useMemo( + () => Boolean(linkedAccounts?.some(item => item.is_syncing)), + [linkedAccounts], + ) + + const loadingValue = useMemo(() => getLoadingValue(data), [data]) + useEffect(() => { if ( Number(dateRange.startDate) !== Number(localDateRange.startDate) @@ -175,37 +97,9 @@ export const ProfitAndLossChart = ({ ) { setLocalDateRange(dateRange) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [dateRange]) - const { data, loaded, pullData } = useProfitAndLossLTM({ - currentDate: startOfMonth(Date.now()), - tagFilter: tagFilter, - }) - - const anyData = useMemo(() => { - return Boolean( - data?.find( - x => - x.income !== 0 - || x.costOfGoodsSold !== 0 - || x.grossProfit !== 0 - || x.operatingExpenses !== 0 - || x.profitBeforeTaxes !== 0 - || x.taxes !== 0 - || x.totalExpenses !== 0, - ), - ) - }, [data]) - - const { data: linkedAccounts } = useLinkedAccounts() - - const isSyncing = useMemo( - () => Boolean(linkedAccounts?.some(item => item.is_syncing)), - [linkedAccounts], - ) - - const loadingValue = useMemo(() => getLoadingValue(data), [data]) - useEffect(() => { if (loaded === 'complete' && data) { const foundCurrent = data.find( @@ -216,7 +110,7 @@ export const ProfitAndLossChart = ({ < Number(localDateRange.endDate), ) - if (!foundCurrent) { + if (!foundCurrent && theData?.length > 1) { const newDate = startOfMonth(localDateRange.startDate) pullData(newDate) return @@ -230,13 +124,14 @@ export const ProfitAndLossChart = ({ < Number(sub(localDateRange.endDate, { months: 1 })), ) - if (!foundBefore) { + if (!foundBefore && theData?.length > 1) { const newDate = startOfMonth( sub(localDateRange.startDate, { months: 1 }), ) pullData(newDate) } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [localDateRange]) useEffect(() => { @@ -252,6 +147,7 @@ export const ProfitAndLossChart = ({ ) { setChartWindow(newChartWindow) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [localDateRange]) useEffect(() => { @@ -262,99 +158,20 @@ export const ProfitAndLossChart = ({ } }, [loaded]) - const getMonthName = (pnl: ProfitAndLossSummaryData | undefined) => - pnl - ? format( - new Date(pnl.year, pnl.month - 1, 1), - compactView ? 'LLLLL' : 'LLL', - ) - : '' - - const summarizePnL = (pnl: ProfitAndLossSummaryData | undefined) => ({ - name: getMonthName(pnl), - revenue: pnl?.income || 0, - revenueUncategorized: pnl?.uncategorizedInflows || 0, - expenses: -(pnl?.totalExpenses || 0), - expensesUncategorized: -(pnl?.uncategorizedOutflows || 0), - totalExpensesInverse: pnl?.totalExpensesInverse || 0, - uncategorizedOutflowsInverse: pnl?.uncategorizedOutflowsInverse || 0, - netProfit: pnl?.netProfit || 0, - selected: - !!pnl - && pnl.month === selectionMonth.month + 1 - && pnl.year === selectionMonth.year, - year: pnl?.year, - month: pnl?.month, - base: 0, - loading: pnl?.isLoading ? loadingValue : 0, - loadingExpenses: pnl?.isLoading ? -loadingValue : 0, - }) - - const theData = useMemo(() => { - if (loaded !== 'complete' || (loaded === 'complete' && !anyData)) { - const loadingData = [] - const today = Date.now() - for (let i = 11; i >= 0; i--) { - const currentDate = sub(today, { months: i }) - loadingData.push({ - name: format(currentDate, compactView ? 'LLLLL' : 'LLL'), - revenue: 0, - revenueUncategorized: 0, - totalExpensesInverse: 0, - uncategorizedOutflowsInverse: 0, - expenses: 0, - expensesUncategorized: 0, - netProfit: 0, - selected: false, - year: currentDate.getFullYear(), - month: currentDate.getMonth() + 1, - loading: 1000 * Math.pow(-1, i + 1) * (((i + 1) % 12) + 1) + 90000, - loadingExpenses: - -1000 * Math.pow(-1, i + 1) * (((i + 1) % 2) + 1) - 90000, - base: 0, - }) - } - return loadingData - } - - return data - ?.map(x => { - const totalExpenses = x.totalExpenses || 0 - if (totalExpenses < 0 || x.uncategorizedOutflows < 0) { - return { - ...x, - totalExpenses: totalExpenses < 0 ? 0 : totalExpenses, - uncategorizedOutflows: - x.uncategorizedOutflows < 0 ? 0 : x.uncategorizedOutflows, - totalExpensesInverse: totalExpenses < 0 ? -totalExpenses : 0, - uncategorizedOutflowsInverse: - x.uncategorizedOutflows < 0 ? -x.uncategorizedOutflows : 0, - } - } - - return x - }) - ?.filter( - x => - differenceInMonths( - startOfMonth(new Date(x.year, x.month - 1, 1)), - chartWindow.start, - ) >= 0 - && differenceInMonths( - startOfMonth(new Date(x.year, x.month - 1, 1)), - chartWindow.start, - ) < 12 - && differenceInMonths( - chartWindow.end, - startOfMonth(new Date(x.year, x.month - 1, 1)), - ) >= 0 - && differenceInMonths( - chartWindow.end, - startOfMonth(new Date(x.year, x.month - 1, 1)), - ) <= 12, - ) - .map(x => summarizePnL(x)) - }, [selectionMonth, chartWindow, data, loaded, compactView]) + /** @TODO temp */ + const len = period === 'year' ? 1 : period === 'quarter' ? 4 : 12 + const theData = useMemo(() => collectData({ + data, + loaded, + loadingValue, + anyData, + chartWindow, + compactView, + selectionMonth, + }).slice(0, len), + [data, loaded, loadingValue, anyData, chartWindow, compactView, selectionMonth, len] + ) + // }), [selectionMonth, chartWindow, data, loaded, compactView]) const onClick: CategoricalChartFunc = ({ activePayload }) => { if (loaded !== 'complete' || !anyData) { @@ -424,32 +241,6 @@ export const ProfitAndLossChart = ({ return null } - const formatYAxisValue = (value?: string | number) => { - if (!value) { - return value - } - - try { - let suffix = '' - const base = Number(value) / 100 - let val = base - - if (Math.abs(base) >= 1000000000) { - suffix = 'B' - val = base / 1000000000 - } else if (Math.abs(base) >= 1000000) { - suffix = 'M' - val = base / 1000000 - } else if (Math.abs(base) >= 1000) { - suffix = 'k' - val = base / 1000 - } - return `${val}${suffix}` - } catch (_err) { - return value - } - } - const renderLegend = (props: LegendProps) => { return (
    @@ -539,7 +330,7 @@ export const ProfitAndLossChart = ({ fill='#F7F8FA' stroke='none' x={points[0].x - width / 2} - y={points[0].y} + y={points[0].y - 12} width={width} height={height} radius={6} @@ -548,7 +339,10 @@ export const ProfitAndLossChart = ({ ) } - const [animateFrom, setAnimateFrom] = useState(-1) + const [barSize, barMargin] = useMemo(() => + getBarSizing((theData ?? []).length, compactView), + [compactView, theData] + ) return (
    @@ -561,13 +355,18 @@ export const ProfitAndLossChart = ({ width='100%' height='100%' onResize={width => { - if (width && width < 620 && !compactView) { - setCompactView(true) + if (width && width < 500 && compactView !== 'xs') { + setCompactView('xs') + return + } + + if (width && width >= 500 && width < 680 && compactView !== 'md') { + setCompactView('md') return } - if (width && width >= 620 && compactView) { - setCompactView(false) + if (width && width >= 680 && compactView !== 'lg') { + setCompactView('lg') return } }} @@ -650,6 +449,7 @@ export const ProfitAndLossChart = ({ } /> @@ -731,6 +535,7 @@ export const ProfitAndLossChart = ({ { + const today = startOfMonth(Date.now()) + const yearAgo = sub(today, { months: 11 }) + const current = startOfMonth(new Date(currentYear, currentMonth - 1, 1)) + + if ( + differenceInMonths(startOfMonth(chartWindow.start), current) < 0 + && differenceInMonths(startOfMonth(chartWindow.end), current) > 1 + ) { + return chartWindow + } + + if (differenceInMonths(startOfMonth(chartWindow.start), current) === 0) { + return { + start: startOfMonth(sub(current, { months: 1 })), + end: endOfMonth(add(current, { months: 11 })), + } + } + + if ( + differenceInMonths(endOfMonth(chartWindow.end), endOfMonth(current)) + === 1 + && differenceInMonths(today, current) >= 1 + ) { + return { + start: startOfMonth(sub(current, { months: 10 })), + end: endOfMonth(add(current, { months: 2 })), + } + } + + if ( + differenceInMonths(current, startOfMonth(chartWindow.end)) === 0 + && differenceInMonths(current, startOfMonth(today)) > 0 + ) { + return { + start: startOfMonth(sub(current, { months: 11 })), + end: endOfMonth(add(current, { months: 1 })), + } + } + + if (current >= yearAgo) { + return { + start: startOfMonth(yearAgo), + end: endOfMonth(today), + } + } + + if (Number(current) > Number(chartWindow.end)) { + return { + start: startOfMonth(sub(current, { months: 12 })), + end: endOfMonth(current), + } + } + + if (differenceInMonths(current, startOfMonth(chartWindow.start)) < 0) { + return { + start: startOfMonth(current), + end: endOfMonth(add(current, { months: 11 })), + } + } + + return chartWindow +} + +// Find a value for a loading bar so it has relatively same height as other +// bars (when data loaded), or just use 10,000 if no data. +export const getLoadingValue = (data?: ProfitAndLossSummaryData[]) => { + if (!data) { + return 10000 + } + + let max = 0 + + data.forEach(x => { + const current = Math.max( + Math.abs(x.income), + Math.abs(Math.abs((x?.income || 0) - (x?.netProfit || 0))), + ) + if (current > max) { + max = current + } + }) + + return max === 0 ? 10000 : max * 0.6 +} + +// Get full or shorten month name based on the view size (full / compact) +export const getMonthName = (pnl: ProfitAndLossSummaryData | undefined, compactView: CompactView) => + pnl + ? format( + new Date(pnl.year, pnl.month - 1, 1), + compactView !== 'lg' ? 'LLLLL' : 'LLL', + ) + : '' + +// Parse ProfitAndLossSummaryData into format used by the chart +export const summarizePnL = ( + pnl: ProfitAndLossSummaryData | undefined, + loadingValue: number, + selectionMonth: { month: number; year: number }, + compactView: CompactView, +) => ({ + name: getMonthName(pnl, compactView), + revenue: pnl?.income || 0, + revenueUncategorized: pnl?.uncategorizedInflows || 0, + expenses: -(pnl?.totalExpenses || 0), + expensesUncategorized: -(pnl?.uncategorizedOutflows || 0), + totalExpensesInverse: pnl?.totalExpensesInverse || 0, + uncategorizedOutflowsInverse: pnl?.uncategorizedOutflowsInverse || 0, + netProfit: pnl?.netProfit || 0, + selected: + !!pnl + && pnl.month === selectionMonth.month + 1 + && pnl.year === selectionMonth.year, + year: pnl?.year, + month: pnl?.month, + base: 0, + loading: pnl?.isLoading ? loadingValue : 0, + loadingExpenses: pnl?.isLoading ? -loadingValue : 0, +}) + +// Format Y-axis labels using denominators like billions, millions, thousands +export const formatYAxisValue = (value?: string | number) => { + if (!value) { + return value + } + + try { + let suffix = '' + const base = Number(value) / 100 + let val = base + + if (Math.abs(base) >= 1000000000) { + suffix = 'B' + val = base / 1000000000 + } else if (Math.abs(base) >= 1000000) { + suffix = 'M' + val = base / 1000000 + } else if (Math.abs(base) >= 1000) { + suffix = 'k' + val = base / 1000 + } + return `${val}${suffix}` + } catch (_err) { + return value + } +} + +// Check if there is any non-zero value in the data +export const hasAnyData = (data: ProfitAndLossSummaryData[]) => ( + Boolean( + data?.find( + x => + x.income !== 0 + || x.costOfGoodsSold !== 0 + || x.grossProfit !== 0 + || x.operatingExpenses !== 0 + || x.profitBeforeTaxes !== 0 + || x.taxes !== 0 + || x.totalExpenses !== 0, + ), + ) +) + +// Parse and filter raw data for the chart. +// If data has not be loaded yet, then it returns data for "loading" bars +export const collectData = ({ + data, + loaded, + loadingValue, + anyData, + chartWindow, + compactView, + selectionMonth, +}: { + data: ProfitAndLossSummaryData[] + loaded?: LoadedStatus + anyData: boolean + chartWindow: { + start: Date; + end: Date; + } + compactView: CompactView + loadingValue: number + selectionMonth: { + year: number; + month: number; + } +}) => { + if (loaded !== 'complete' || (loaded === 'complete' && !anyData)) { + const loadingData = [] + const today = Date.now() + for (let i = 11; i >= 0; i--) { + const currentDate = sub(today, { months: i }) + loadingData.push({ + name: format(currentDate, compactView !== 'lg' ? 'LLLLL' : 'LLL'), + revenue: 0, + revenueUncategorized: 0, + totalExpensesInverse: 0, + uncategorizedOutflowsInverse: 0, + expenses: 0, + expensesUncategorized: 0, + netProfit: 0, + selected: false, + year: currentDate.getFullYear(), + month: currentDate.getMonth() + 1, + loading: 1000 * Math.pow(-1, i + 1) * (((i + 1) % 12) + 1) + 90000, + loadingExpenses: + -1000 * Math.pow(-1, i + 1) * (((i + 1) % 2) + 1) - 90000, + base: 0, + }) + } + return loadingData + } + + return data + ?.map(x => { + const totalExpenses = x.totalExpenses || 0 + if (totalExpenses < 0 || x.uncategorizedOutflows < 0) { + return { + ...x, + totalExpenses: totalExpenses < 0 ? 0 : totalExpenses, + uncategorizedOutflows: + x.uncategorizedOutflows < 0 ? 0 : x.uncategorizedOutflows, + totalExpensesInverse: totalExpenses < 0 ? -totalExpenses : 0, + uncategorizedOutflowsInverse: + x.uncategorizedOutflows < 0 ? -x.uncategorizedOutflows : 0, + } + } + + return x + }) + ?.filter( + x => + differenceInMonths( + startOfMonth(new Date(x.year, x.month - 1, 1)), + chartWindow.start, + ) >= 0 + && differenceInMonths( + startOfMonth(new Date(x.year, x.month - 1, 1)), + chartWindow.start, + ) < 12 + && differenceInMonths( + chartWindow.end, + startOfMonth(new Date(x.year, x.month - 1, 1)), + ) >= 0 + && differenceInMonths( + chartWindow.end, + startOfMonth(new Date(x.year, x.month - 1, 1)), + ) <= 12, + ) + .map(x => summarizePnL(x, loadingValue, selectionMonth, compactView)) +} + +// Returns [barSize, margin] for chart based on number of bars and view mode +export const getBarSizing = (itemsLength: number, view: CompactView) => { + const divider = view === 'xs' ? 2 : view === 'md' ? 1.5 : 1 + let base = Math.round(240 / divider) + + if (itemsLength >= 12) { + base = 20 + } else if (itemsLength >= 8) { + base = 40 + } else if (itemsLength >= 6) { + base = 60 + } else if (itemsLength >= 4) { + base = 80 + } + + const margin = Math.max(Math.min(Math.round(base * 0.25 / divider), 24), 2) + + // [barSize, margin] + return [base / divider, margin] +} diff --git a/src/components/ProfitAndLossDatePicker/ProfitAndLossDatePicker.tsx b/src/components/ProfitAndLossDatePicker/ProfitAndLossDatePicker.tsx index 6a3029a9a..d6caf2582 100644 --- a/src/components/ProfitAndLossDatePicker/ProfitAndLossDatePicker.tsx +++ b/src/components/ProfitAndLossDatePicker/ProfitAndLossDatePicker.tsx @@ -9,18 +9,47 @@ import { } from '../DatePicker/ModeSelector/DatePickerModeSelector' import { DatePickerModeSelector } from '../DatePicker/ModeSelector/DatePickerModeSelector' import { ProfitAndLoss } from '../ProfitAndLoss' -import { endOfMonth, startOfMonth } from 'date-fns' +import { endOfMonth, endOfQuarter, endOfYear, startOfMonth, startOfQuarter, startOfYear } from 'date-fns' +import { Select } from '../Input' +import { Period } from '../../hooks/useProfitAndLoss/useProfitAndLoss' -export type ProfitAndLossDatePickerProps = TimeRangePickerConfig +export type ProfitAndLossDatePickerProps = TimeRangePickerConfig & { enablePeriods?: boolean } + +const PERIOD_OPTIONS = [ + { label: 'Compare 12 months', value: 'month' }, + { label: 'Compare quarter', value: 'quarter' }, + { label: 'Compare year', value: 'year' }, +] + +const getDateRange = (date: Date, mode: DatePickerMode) => { + switch(mode) { + case 'quarterPicker': + return { + startDate: startOfQuarter(date), + endDate: endOfQuarter(date), + } + case 'yearPicker': + return { + startDate: startOfYear(date), + endDate: endOfYear(date), + } + default: + return { + startDate: startOfMonth(date), + endDate: endOfMonth(date), + } + } +} export const ProfitAndLossDatePicker = ({ allowedDatePickerModes, datePickerMode: deprecated_datePickerMode, defaultDatePickerMode, customDateRanges, + enablePeriods = false, }: ProfitAndLossDatePickerProps) => { const { business } = useLayerContext() - const { changeDateRange, dateRange } = useContext(ProfitAndLoss.Context) + const { changeDateRange, dateRange, period, setPeriod } = useContext(ProfitAndLoss.Context) const { refetch, compareMode, compareMonths } = useContext( ProfitAndLoss.ComparisonContext, ) @@ -67,25 +96,50 @@ export const ProfitAndLossDatePicker = ({ } return ( - { - if (!Array.isArray(date)) { - getComparisonData(date) - changeDateRange({ - startDate: startOfMonth(date), - endDate: endOfMonth(date), - }) - } - }} - minDate={minDate} - slots={{ - ModeSelector: DatePickerModeSelector, - }} - /> + <> + {enablePeriods && ( +
    + - +
    ) } diff --git a/src/components/ProfitAndLossHeader/ProfitAndLossHeader.tsx b/src/components/ProfitAndLossHeader/ProfitAndLossHeader.tsx index ef1c8447e..41084b66f 100644 --- a/src/components/ProfitAndLossHeader/ProfitAndLossHeader.tsx +++ b/src/components/ProfitAndLossHeader/ProfitAndLossHeader.tsx @@ -9,6 +9,7 @@ export interface ProfitAndLossHeaderProps { text?: string className?: string headingClassName?: string + size?: 'primary' | 'secondary' | 'view' withDatePicker?: boolean } @@ -16,6 +17,7 @@ export const ProfitAndLossHeader = ({ text, className, headingClassName, + size = 'view', withDatePicker, }: ProfitAndLossHeaderProps) => { const { data: linkedAccounts } = useLinkedAccounts() @@ -28,7 +30,7 @@ export const ProfitAndLossHeader = ({ return (
    - + {text || 'Profit & Loss'} {isSyncing && } diff --git a/src/hooks/useProfitAndLoss/useProfitAndLossLTM.tsx b/src/hooks/useProfitAndLoss/useProfitAndLossLTM.tsx index 89e2f4623..2b53131d9 100644 --- a/src/hooks/useProfitAndLoss/useProfitAndLossLTM.tsx +++ b/src/hooks/useProfitAndLoss/useProfitAndLossLTM.tsx @@ -75,8 +75,6 @@ export const useProfitAndLossLTM: UseProfitAndLossLTMReturn = ( return buildDates({ currentDate: date }) }, [date, businessId, tagFilter, reportingBasis]) - console.log('period', period) - const queryKey = businessId && Boolean(startYear) diff --git a/src/index.tsx b/src/index.tsx index d3e6aea70..c0b2f463c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -46,7 +46,7 @@ export { BookkeepingUpsellBar } from './components/UpsellBanner' ======================= Composite Views ======================= */ export { BookkeepingOverview } from './views/BookkeepingOverview' -export { AccountingOverview } from './views/AccountingOverview' +export { AccountingOverview, AccountingOverviewV2 } from './views/AccountingOverview' export { BankTransactionsWithLinkedAccounts } from './views/BankTransactionsWithLinkedAccounts' export { GeneralLedgerView } from './views/GeneralLedger' export { ProjectProfitabilityView } from './views/ProjectProfitability' diff --git a/src/styles/accounting_overview.scss b/src/styles/accounting_overview.scss index 312c44e39..3736d194f 100644 --- a/src/styles/accounting_overview.scss +++ b/src/styles/accounting_overview.scss @@ -6,21 +6,24 @@ max-width: 1406px; } -.Layer__component.Layer__accounting-overview-profit-and-loss - .recharts-responsive-container { - margin-top: -42px; +.accounting-overview-profit-and-loss-chart-controls { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-md); + border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); } +// .Layer__component.Layer__accounting-overview-profit-and-loss +// .recharts-responsive-container { +// margin-top: -42px; +// } + .Layer__accounting-overview-profit-and-loss-charts > .Layer__toggle { display: none; } -@container (max-width: 1023px) { - .Layer__accounting-overview-profit-and-loss .recharts-legend-wrapper { - margin-top: -40px; - } -} - @container (max-width: 796px) { .Layer__accounting-overview-profit-and-loss-charts { flex-direction: column; diff --git a/src/styles/charts.scss b/src/styles/charts.scss index 12da46d2d..410dc1376 100644 --- a/src/styles/charts.scss +++ b/src/styles/charts.scss @@ -12,9 +12,9 @@ } } -.Layer__component .recharts-responsive-container.Layer__chart-container { - padding-top: 42px; -} +// .Layer__component .recharts-responsive-container.Layer__chart-container { +// padding-top: 42px; +// } .Layer__chart-wrapper { position: relative; @@ -24,6 +24,21 @@ min-height: 300px; } +// .Layer__chart-header { +// display: flex; +// align-items: center; +// justify-content: space-between; +// padding: var(--spacing-md) var(--spacing-xl); + +// & .Layer__chart-legend-list { +// justify-self: flex-end; +// } + +// @container (max-width: 1400px) { +// padding: var(--spacing-md); +// } +// } + .Layer__chart-legend-list { margin: 0; padding: 0; diff --git a/src/styles/datepicker.scss b/src/styles/datepicker.scss index 7877104e4..51ae96e80 100644 --- a/src/styles/datepicker.scss +++ b/src/styles/datepicker.scss @@ -1,5 +1,11 @@ @import '../components/DatePicker/ModeSelector/date-picker-mode-selector.scss'; +.Layer__datepicker__container { + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); +} + .Layer__datepicker__wrapper, #Layer__datepicker__portal { display: inline-flex; diff --git a/src/styles/profit_and_loss.scss b/src/styles/profit_and_loss.scss index de110008d..6344357fe 100644 --- a/src/styles/profit_and_loss.scss +++ b/src/styles/profit_and_loss.scss @@ -225,31 +225,31 @@ font-size: 0.75rem; } -.Layer__profit-and-loss-chart .recharts-legend-wrapper { - margin-top: -46px; -} +// .Layer__profit-and-loss-chart .recharts-legend-wrapper { +// margin-top: -46px; +// } -.Layer__profit-and-loss-chart .recharts-legend-item-text { +.Layer__profit-and-loss-chart .Layer__profit-and-loss__legend-item-text { font-size: 12px; color: var(--color-base-700); vertical-align: middle; } -.Layer__profit-and-loss-chart .recharts-legend-item { +.Layer__profit-and-loss-chart .Layer__profit-and-loss__legend-item { fill: var(--bar-color-income); vertical-align: middle; } -.Layer__profit-and-loss-chart .legend-item-0 { +.Layer__chart-legend-list .legend-item-0 { fill: var(--bar-color-income); } -.Layer__profit-and-loss-chart .legend-item-1 { +.Layer__chart-legend-list .legend-item-1 { fill: var(--bar-color-expenses); } -.Layer__profit-and-loss-chart .legend-item-2 { - fill: var(--base-transparent-16-light); +.Layer__chart-legend-list .legend-item-2 { + fill: url(#layer-bar-stripe-pattern-dark); } .Layer__profit-and-loss-chart__selection-indicator { @@ -258,6 +258,109 @@ transition: opacity 0.1s linear; } +.Layer__profit-and-loss__chart-header { + .Layer__profit-and-loss__legend-item { + white-space: nowrap; + } + + &.Layer__profit-and-loss__chart-header--no-date-picker { + @container (max-width: 600px) { + .Layer__header__row { + box-sizing: border-box; + flex-direction: column; + align-items: flex-start; + padding: var(--spacing-md); + gap: var(--spacing-sm); + } + + .Layer__header__col { + padding: 0; + min-height: auto; + } + } + + @container (max-width: 360px) { + .Layer__chart-legend-list { + flex-direction: column; + align-items: flex-start; + } + } + } + + &.Layer__profit-and-loss__chart-header--with-date-picker-extended { + .Layer__profit-and-loss__chart-header__legend-row { + display: none; + } + + @container (max-width: 800px) { + .Layer__header__row { + box-sizing: border-box; + flex-direction: column; + align-items: flex-start; + padding: var(--spacing-md); + gap: var(--spacing-md); + } + + .Layer__profit-and-loss__chart-header__legend-col { + display: none; + } + + .Layer__profit-and-loss__chart-header__legend-row { + display: flex; + align-items: flex-end; + } + + .Layer__header__col { + padding: 0; + min-height: auto; + } + } + + @container (max-width: 420px) { + .Layer__datepicker__container { + flex-wrap: wrap; + + .react-datepicker__input-container input { + max-width: 106px; + } + } + + .Layer__profit-and-loss__chart-header__legend-row { + align-items: flex-start; + } + } + } + + &.Layer__profit-and-loss__chart-header--with-date-picker { + .Layer__profit-and-loss__chart-header__legend-row { + display: none; + } + + @container (max-width: 540px) { + .Layer__header__row { + box-sizing: border-box; + flex-direction: column; + align-items: flex-start; + padding: var(--spacing-md); + gap: var(--spacing-md); + } + + .Layer__profit-and-loss__chart-header__legend-col { + display: none; + } + + .Layer__profit-and-loss__chart-header__legend-row { + display: flex; + } + + .Layer__header__col { + padding: 0; + min-height: auto; + } + } + } +} + /** ---- */ .Layer__profit-and-loss__chart_with_summaries { display: flex; @@ -311,6 +414,10 @@ fill: var(--bar-color-expenses); } +.Layer__profit-and-loss__chart .Layer__chart-container.recharts-responsive-container { + padding-top: var(--spacing-md); +} + .Layer__chart-container--loading .Layer__profit-and-loss-chart__bar--expenses { fill: var(--color-base-50); animation: layer_chart_bar_loading_anim 2s linear infinite; diff --git a/src/styles/typography.scss b/src/styles/typography.scss index d1a743540..be9b0a12d 100644 --- a/src/styles/typography.scss +++ b/src/styles/typography.scss @@ -2,6 +2,7 @@ color: var(--text-color-primary); font-size: var(--text-heading); font-weight: var(--font-weight-bold); + font-variation-settings: 'wght' var(--font-weight-bold); line-height: 140%; letter-spacing: -0.12px; margin: 0; @@ -13,6 +14,7 @@ color: var(--text-color-primary); font-size: var(--text-heading-sm); font-weight: var(--font-weight-bold); + font-variation-settings: 'wght' var(--font-weight-bold); } .Layer__heading--view, @@ -20,6 +22,7 @@ color: var(--text-color-primary); font-size: var(--text-heading-view); font-weight: var(--font-weight-bold); + font-variation-settings: 'wght' var(--font-weight-bold); } .Layer__heading--page, @@ -27,6 +30,7 @@ color: var(--text-color-primary); font-size: var(--text-heading-page); font-weight: var(--font-weight-bold); + font-variation-settings: 'wght' var(--font-weight-bold); } .Layer__text { diff --git a/src/views/AccountingOverview/AccountingOverview.tsx b/src/views/AccountingOverview/AccountingOverview.tsx index 63d6d5087..3c174c854 100644 --- a/src/views/AccountingOverview/AccountingOverview.tsx +++ b/src/views/AccountingOverview/AccountingOverview.tsx @@ -80,7 +80,7 @@ export const AccountingOverview = ({
    - +
    @@ -114,10 +114,9 @@ export const AccountingOverview = ({ asWidget elevated={true} > - void + middleBanner?: ReactNode + chartColorsList?: string[] + stringOverrides?: AccountingOverviewStringOverrides + tagFilter?: TagOption + showTransactionsToReview?: boolean + slotProps?: { + profitAndLoss?: { + summaries?: { + variants?: Variants + } + } + } +} + +type PnlToggleOption = 'revenue' | 'expenses' + +export const AccountingOverviewV2 = ({ + title = 'Accounting overview', + showTitle = true, + enableOnboarding = false, + onboardingStepOverride = undefined, + onTransactionsToReviewClick, + middleBanner, + chartColorsList, + stringOverrides, + tagFilter = undefined, + showTransactionsToReview = true, + slotProps, +}: AccountingOverviewProps) => { + const [pnlToggle, setPnlToggle] = useState('expenses') + + const profitAndLossSummariesVariants = + slotProps?.profitAndLoss?.summaries?.variants + + return ( + + + {enableOnboarding && ( + + )} + + + + {middleBanner && ( + + {middleBanner} + + )} + , + ] + : undefined, + }} + variants={profitAndLossSummariesVariants} + /> +
    + setPnlToggle(e.target.value as PnlToggleOption)} + /> + + + + + + +
    +
    +
    + ) +} diff --git a/src/views/AccountingOverview/index.tsx b/src/views/AccountingOverview/index.tsx index cbca02324..ffdad07bb 100644 --- a/src/views/AccountingOverview/index.tsx +++ b/src/views/AccountingOverview/index.tsx @@ -1 +1,2 @@ export { AccountingOverview } from './AccountingOverview' +export { AccountingOverviewV2 } from './AccountingOverviewV2' From 24f718a5387b5dd2490f3ad71fa9acb6303ed8bd Mon Sep 17 00:00:00 2001 From: Tom Antas Date: Fri, 29 Nov 2024 18:37:42 +0100 Subject: [PATCH 4/5] clean --- src/components/DatePicker/DatePicker.tsx | 3 ++ .../ProfitAndLossChart/ProfitAndLossChart.tsx | 44 ++++++++----------- src/components/ProfitAndLossChart/utils.ts | 22 +++++----- .../useProfitAndLoss/useProfitAndLoss.tsx | 2 + src/styles/accounting_overview.scss | 5 --- src/styles/charts.scss | 19 -------- src/styles/profit_and_loss.scss | 4 -- 7 files changed, 34 insertions(+), 65 deletions(-) diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 38036e017..f2c147085 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -138,6 +138,7 @@ export const DatePicker = ({ } catch (_err) { return } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selected]) useEffect(() => { @@ -149,12 +150,14 @@ export const DatePicker = ({ } else { setPickerDate(false) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDates]) useEffect(() => { if (isRangeMode(mode)) { setSelectedDates([startDate, endDate]) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [startDate, endDate]) const wrapperClassNames = classNames( diff --git a/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx b/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx index 5130ec279..caedc99bb 100644 --- a/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx +++ b/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx @@ -6,7 +6,7 @@ import { } from '../../hooks/useProfitAndLoss/useProfitAndLossLTM' import { centsToDollars } from '../../models/Money' import { isDateAllowedToBrowse } from '../../utils/business' -import { ProfitAndLoss as PNL, ProfitAndLoss } from '../ProfitAndLoss' +import { ProfitAndLoss as PNL } from '../ProfitAndLoss' import { Heading, HeadingSize, Text } from '../Typography' import { ChartStateCard } from './ChartStateCard' import { Indicator } from './Indicator' @@ -35,7 +35,7 @@ import { CategoricalChartFunc } from 'recharts/types/chart/generateCategoricalCh import { collectData, formatYAxisValue, getBarSizing, getChartWindow, getLoadingValue, hasAnyData } from './utils' import { Header, HeaderCol, HeaderRow } from '../Header' -export type CompactView = 'xs' | 'md' | 'lg' +export type ViewSize = 'xs' | 'md' | 'lg' export interface Props { title?: string @@ -65,7 +65,7 @@ export const ProfitAndLossChart = ({ period, }) - const [compactView, setCompactView] = useState('lg') + const [viewSize, setViewSize] = useState('lg') const [localDateRange, setLocalDateRange] = useState(dateRange) const [customCursorSize, setCustomCursorSize] = useState({ width: 0, @@ -172,13 +172,13 @@ export const ProfitAndLossChart = ({ loadingValue, anyData, chartWindow, - compactView, + viewSize, selectionMonth, }).slice(0, len), - [data, loaded, loadingValue, anyData, chartWindow, compactView, selectionMonth, len] + [data, loaded, loadingValue, anyData, chartWindow, viewSize, selectionMonth, len] ) // @TODO - Tom - // }), [selectionMonth, chartWindow, data, loaded, compactView]) + // }), [selectionMonth, chartWindow, data, loaded, viewSize]) const onClick: CategoricalChartFunc = ({ activePayload }) => { if (loaded !== 'complete' || !anyData) { @@ -289,8 +289,8 @@ export const ProfitAndLossChart = ({ } const [barSize, barMargin] = useMemo(() => - getBarSizing((theData ?? []).length, compactView), - [compactView, theData] + getBarSizing((theData ?? []).length, viewSize), + [viewSize, theData] ) return ( @@ -302,7 +302,7 @@ export const ProfitAndLossChart = ({ {!withDatePicker && ( - + )} @@ -310,15 +310,15 @@ export const ProfitAndLossChart = ({ <> - + - + - + @@ -333,18 +333,18 @@ export const ProfitAndLossChart = ({ width='100%' height='100%' onResize={width => { - if (width && width < 500 && compactView !== 'xs') { - setCompactView('xs') + if (width && width < 500 && viewSize !== 'xs') { + setViewSize('xs') return } - if (width && width >= 500 && width < 680 && compactView !== 'md') { - setCompactView('md') + if (width && width >= 500 && width < 680 && viewSize !== 'md') { + setViewSize('md') return } - if (width && width >= 680 && compactView !== 'lg') { - setCompactView('lg') + if (width && width >= 680 && viewSize !== 'lg') { + setViewSize('lg') return } }} @@ -405,7 +405,6 @@ export const ProfitAndLossChart = ({ { return max === 0 ? 10000 : max * 0.6 } -// Get full or shorten month name based on the view size (full / compact) -export const getMonthName = (pnl: ProfitAndLossSummaryData | undefined, compactView: CompactView) => +// Get full or shorten month name based on the view size +export const getMonthName = (pnl: ProfitAndLossSummaryData | undefined, viewSize: ViewSize) => pnl ? format( new Date(pnl.year, pnl.month - 1, 1), - compactView !== 'lg' ? 'LLLLL' : 'LLL', + viewSize !== 'lg' ? 'LLLLL' : 'LLL', ) : '' @@ -119,9 +119,9 @@ export const summarizePnL = ( pnl: ProfitAndLossSummaryData | undefined, loadingValue: number, selectionMonth: { month: number; year: number }, - compactView: CompactView, + viewSize: ViewSize, ) => ({ - name: getMonthName(pnl, compactView), + name: getMonthName(pnl, viewSize), revenue: pnl?.income || 0, revenueUncategorized: pnl?.uncategorizedInflows || 0, expenses: -(pnl?.totalExpenses || 0), @@ -191,7 +191,7 @@ export const collectData = ({ loadingValue, anyData, chartWindow, - compactView, + viewSize, selectionMonth, }: { data: ProfitAndLossSummaryData[] @@ -201,7 +201,7 @@ export const collectData = ({ start: Date; end: Date; } - compactView: CompactView + viewSize: ViewSize loadingValue: number selectionMonth: { year: number; @@ -214,7 +214,7 @@ export const collectData = ({ for (let i = 11; i >= 0; i--) { const currentDate = sub(today, { months: i }) loadingData.push({ - name: format(currentDate, compactView !== 'lg' ? 'LLLLL' : 'LLL'), + name: format(currentDate, viewSize !== 'lg' ? 'LLLLL' : 'LLL'), revenue: 0, revenueUncategorized: 0, totalExpensesInverse: 0, @@ -270,11 +270,11 @@ export const collectData = ({ startOfMonth(new Date(x.year, x.month - 1, 1)), ) <= 12, ) - .map(x => summarizePnL(x, loadingValue, selectionMonth, compactView)) + .map(x => summarizePnL(x, loadingValue, selectionMonth, viewSize)) } // Returns [barSize, margin] for chart based on number of bars and view mode -export const getBarSizing = (itemsLength: number, view: CompactView) => { +export const getBarSizing = (itemsLength: number, view: ViewSize) => { const divider = view === 'xs' ? 2 : view === 'md' ? 1.5 : 1 let base = Math.round(240 / divider) diff --git a/src/hooks/useProfitAndLoss/useProfitAndLoss.tsx b/src/hooks/useProfitAndLoss/useProfitAndLoss.tsx index 75d1924ee..aee4e7504 100644 --- a/src/hooks/useProfitAndLoss/useProfitAndLoss.tsx +++ b/src/hooks/useProfitAndLoss/useProfitAndLoss.tsx @@ -198,6 +198,7 @@ export const useProfitAndLoss: UseProfitAndLoss = ( const withShare = applyShare(sorted, total) return { filteredDataRevenue: withShare, filteredTotalRevenue: total } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, startDate, filters, sidebarScope, summaryData]) const { filteredDataExpenses, filteredTotalExpenses } = useMemo(() => { @@ -261,6 +262,7 @@ export const useProfitAndLoss: UseProfitAndLoss = ( const withShare = applyShare(sorted, total) return { filteredDataExpenses: withShare, filteredTotalExpenses: total } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, startDate, filters, sidebarScope, summaryData]) return { diff --git a/src/styles/accounting_overview.scss b/src/styles/accounting_overview.scss index 3736d194f..c8abc74fb 100644 --- a/src/styles/accounting_overview.scss +++ b/src/styles/accounting_overview.scss @@ -15,11 +15,6 @@ border-bottom: 1px solid var(--border-color); } -// .Layer__component.Layer__accounting-overview-profit-and-loss -// .recharts-responsive-container { -// margin-top: -42px; -// } - .Layer__accounting-overview-profit-and-loss-charts > .Layer__toggle { display: none; } diff --git a/src/styles/charts.scss b/src/styles/charts.scss index 410dc1376..0cb2fa3c7 100644 --- a/src/styles/charts.scss +++ b/src/styles/charts.scss @@ -12,10 +12,6 @@ } } -// .Layer__component .recharts-responsive-container.Layer__chart-container { -// padding-top: 42px; -// } - .Layer__chart-wrapper { position: relative; } @@ -24,21 +20,6 @@ min-height: 300px; } -// .Layer__chart-header { -// display: flex; -// align-items: center; -// justify-content: space-between; -// padding: var(--spacing-md) var(--spacing-xl); - -// & .Layer__chart-legend-list { -// justify-self: flex-end; -// } - -// @container (max-width: 1400px) { -// padding: var(--spacing-md); -// } -// } - .Layer__chart-legend-list { margin: 0; padding: 0; diff --git a/src/styles/profit_and_loss.scss b/src/styles/profit_and_loss.scss index 6344357fe..23493ecf8 100644 --- a/src/styles/profit_and_loss.scss +++ b/src/styles/profit_and_loss.scss @@ -225,10 +225,6 @@ font-size: 0.75rem; } -// .Layer__profit-and-loss-chart .recharts-legend-wrapper { -// margin-top: -46px; -// } - .Layer__profit-and-loss-chart .Layer__profit-and-loss__legend-item-text { font-size: 12px; color: var(--color-base-700); From 8ca605565c5afbb9a556ff7a609f93334342a9b0 Mon Sep 17 00:00:00 2001 From: Tom Antas Date: Fri, 29 Nov 2024 19:08:37 +0100 Subject: [PATCH 5/5] minimal version --- .../ProfitAndLossChart/ProfitAndLossChart.tsx | 56 ++++++++++--------- src/styles/profit_and_loss.scss | 12 ++++ .../AccountingOverview/AccountingOverview.tsx | 1 - 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx b/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx index caedc99bb..2ac160538 100644 --- a/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx +++ b/src/components/ProfitAndLossChart/ProfitAndLossChart.tsx @@ -294,36 +294,42 @@ export const ProfitAndLossChart = ({ ) return ( -
    -
    - - - {title} - - {!withDatePicker && ( +
    + {title || withDatePicker ? ( +
    + - + {title} - )} - - {withDatePicker && ( - <> - - - - - - - - - + {!withDatePicker && ( - - - )} -
    + )} + + {withDatePicker && ( + <> + + + + + + + + + + + + + + + )} +
    + ) : ( +
    + +
    + )}