Skip to content

Commit

Permalink
refactor: extract month slider into its own component
Browse files Browse the repository at this point in the history
This makes it possible to use the slider on other pages. I am currently working on a statistics page which uses the slider as well.
Merging this refactor prevents future merge conflicts.
  • Loading branch information
ekeih committed Feb 2, 2024
1 parent 21b7665 commit bf1c5be
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 109 deletions.
113 changes: 10 additions & 103 deletions src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,22 @@
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useEffect, useState, useCallback } from 'react'
import { useSearchParams, Link, useNavigate } from 'react-router-dom'
import { safeName } from '../lib/name-helper'
import { Budget, BudgetMonth, Translation, UUID } from '../types'
import { CalendarDaysIcon } from '@heroicons/react/24/solid'
import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/20/solid'
import { useSearchParams } from 'react-router-dom'
import { get } from '../lib/api/base'
import { monthYearFromDate } from '../lib/dates'
import { formatMoney } from '../lib/format'
import isSupported from '../lib/is-supported'
import LoadingSpinner from './LoadingSpinner'
import Error from './Error'
import {
dateFromMonthYear,
monthYearFromDate,
translatedMonthFormat,
shortTranslatedMonthFormat,
} from '../lib/dates'
import { replaceMonthInLinks } from '../lib/month-helper'
import { safeName } from '../lib/name-helper'
import { Budget, BudgetMonth, Translation, UUID } from '../types'
import CategoryMonth from './CategoryMonth'
import MonthPicker from './MonthPicker'
import Error from './Error'
import LoadingSpinner from './LoadingSpinner'
import MonthSlider from './MonthSlider'
import QuickAllocationForm from './QuickAllocationForm'
import { replaceMonthInLinks } from '../lib/month-helper'

type DashboardProps = { budget: Budget }

const previousMonth = (yearMonth: string) => {
const [year, month] = yearMonth.split('-')

if (month === '01') {
return new Date(parseInt(year) - 1, 11, 15)
} else {
return new Date(parseInt(year), parseInt(month) - 2, 15)
}
}

const nextMonth = (yearMonth: string) => {
const [year, month] = yearMonth.split('-')

if (month === '12') {
return new Date(parseInt(year) + 1, 0, 15)
} else {
return new Date(parseInt(year), parseInt(month), 15)
}
}

const linkToMonth = (month: string) => `/?month=${month}`

const Dashboard = ({ budget }: DashboardProps) => {
const { t }: Translation = useTranslation()
const navigate = useNavigate()

const [searchParams] = useSearchParams()
const activeMonth =
Expand All @@ -56,11 +25,8 @@ const Dashboard = ({ budget }: DashboardProps) => {
const [budgetMonth, setBudgetMonth] = useState<BudgetMonth>()
const [error, setError] = useState('')
const [isLoading, setIsLoading] = useState(true)
const [showMonthPicker, setShowMonthPicker] = useState(false)
const [editingEnvelope, setEditingEnvelope] = useState<UUID>()

const useNativeMonthPicker = isSupported.inputTypeMonth()

const loadBudgetMonth = useCallback(async () => {
return get(replaceMonthInLinks(budget.links.month, activeMonth))
.then(data => {
Expand Down Expand Up @@ -89,66 +55,7 @@ const Dashboard = ({ budget }: DashboardProps) => {
<h1>{safeName(budget, 'budget')}</h1>
</div>

<div className="month-slider">
<Link
to={linkToMonth(monthYearFromDate(previousMonth(activeMonth)))}
title={translatedMonthFormat.format(previousMonth(activeMonth))}
onClick={() => setIsLoading(true)}
>
<ChevronLeftIcon className="inline h-6" />
{shortTranslatedMonthFormat.format(previousMonth(activeMonth))}
</Link>
<div className="border-red-800 dark:border-red-600">
{useNativeMonthPicker ? (
<div className="text-center">
<label htmlFor="month" className="sr-only">
{t('dashboard.selectMonth')}
</label>
<input
type="month"
id="month"
value={activeMonth}
className="cursor-pointer border-none bg-transparent text-center"
onChange={e => {
e.preventDefault()
navigate(linkToMonth(e.target.value))
}}
/>
</div>
) : (
<div className="flex items-center justify-center">
<span className="mr-2 text-center">
{translatedMonthFormat.format(dateFromMonthYear(activeMonth))}
</span>
<button
type="button"
title={t('dashboard.selectMonth')}
onClick={() => {
setShowMonthPicker(!showMonthPicker)
}}
>
<CalendarDaysIcon className="icon" />
</button>
</div>
)}
</div>
<Link
to={linkToMonth(monthYearFromDate(nextMonth(activeMonth)))}
title={translatedMonthFormat.format(nextMonth(activeMonth))}
onClick={() => setIsLoading(true)}
>
{shortTranslatedMonthFormat.format(nextMonth(activeMonth))}
<ChevronRightIcon className="inline h-6" />
</Link>
</div>

{useNativeMonthPicker ? null : (
<MonthPicker
open={showMonthPicker}
setOpen={setShowMonthPicker}
activeMonth={activeMonth}
/>
)}
<MonthSlider budget={budget} setIsLoading={setIsLoading} />

{isLoading || !budgetMonth ? (
<LoadingSpinner />
Expand Down
13 changes: 7 additions & 6 deletions src/components/MonthPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Fragment, SetStateAction, Dispatch, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { translatedMonthFormat } from '../lib/dates'
import { ArrowLongRightIcon, XMarkIcon } from '@heroicons/react/20/solid'
import { Dispatch, Fragment, SetStateAction, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { translatedMonthFormat } from '../lib/dates'
import { Translation } from '../types'
import { useTranslation } from 'react-i18next'

type Props = {
open: boolean
setOpen: Dispatch<SetStateAction<boolean>>
activeMonth: string
route?: string
}

const MonthPicker = ({ open, setOpen, activeMonth }: Props) => {
const MonthPicker = ({ open, setOpen, activeMonth, route = '' }: Props) => {
const { t }: Translation = useTranslation()
const [selectedDate, setSelectedDate] = useState(`${activeMonth}-01`)

Expand Down Expand Up @@ -72,7 +73,7 @@ const MonthPicker = ({ open, setOpen, activeMonth }: Props) => {
}}
></input>
<Link
to={`/?month=${selectedDate}`}
to={`/${route}?month=${selectedDate}`}
onClick={() => setOpen(false)}
className="pl-2"
title={translatedMonthFormat.format(
Expand All @@ -83,7 +84,7 @@ const MonthPicker = ({ open, setOpen, activeMonth }: Props) => {
</Link>
</div>
<Link
to="/"
to={`/${route}`}
className="link-blue block pt-2 text-end text-sm"
onClick={() => setOpen(false)}
>
Expand Down
126 changes: 126 additions & 0 deletions src/components/MonthSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
CalendarDaysIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from '@heroicons/react/20/solid'
import { Dispatch, SetStateAction, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link, useNavigate, useSearchParams } from 'react-router-dom'

import {
dateFromMonthYear,
monthYearFromDate,
shortTranslatedMonthFormat,
translatedMonthFormat,
} from '../lib/dates'
import isSupported from '../lib/is-supported'
import { Budget, Translation } from '../types'
import MonthPicker from './MonthPicker'

type Props = {
budget: Budget
route?: string
setIsLoading: Dispatch<SetStateAction<boolean>>
}

const previousMonth = (yearMonth: string) => {
const [year, month] = yearMonth.split('-')

if (month === '01') {
return new Date(parseInt(year) - 1, 11, 15)
} else {
return new Date(parseInt(year), parseInt(month) - 2, 15)
}
}

const nextMonth = (yearMonth: string) => {
const [year, month] = yearMonth.split('-')

if (month === '12') {
return new Date(parseInt(year) + 1, 0, 15)
} else {
return new Date(parseInt(year), parseInt(month), 15)
}
}

const MonthSlider = ({ budget, route = '', setIsLoading }: Props) => {
const { t }: Translation = useTranslation()
const navigate = useNavigate()

const [searchParams] = useSearchParams()
const activeMonth =
searchParams.get('month')?.substring(0, 7) || monthYearFromDate(new Date())

const [showMonthPicker, setShowMonthPicker] = useState(false)
const useNativeMonthPicker = isSupported.inputTypeMonth()

const linkToMonth = (month: string) => `/${route}?month=${month}`

return (
<div>
<div className="month-slider">
<Link
to={linkToMonth(monthYearFromDate(previousMonth(activeMonth)))}
title={translatedMonthFormat.format(previousMonth(activeMonth))}
onClick={() => setIsLoading(true)}
>
<ChevronLeftIcon className="inline h-6" />
{shortTranslatedMonthFormat.format(previousMonth(activeMonth))}
</Link>
<div className="border-red-800 dark:border-red-600">
{useNativeMonthPicker ? (
<div className="text-center">
<label htmlFor="month" className="sr-only">
{t('dashboard.selectMonth')}
</label>
<input
type="month"
id="month"
value={activeMonth}
className="cursor-pointer border-none bg-transparent text-center"
onChange={e => {
e.preventDefault()
navigate(linkToMonth(e.target.value))
}}
/>
</div>
) : (
<div className="flex items-center justify-center">
<span className="mr-2 text-center">
{translatedMonthFormat.format(dateFromMonthYear(activeMonth))}
</span>
<button
type="button"
title={t('dashboard.selectMonth')}
onClick={() => {
setShowMonthPicker(!showMonthPicker)
}}
>
<CalendarDaysIcon className="icon" />
</button>
</div>
)}
</div>
<Link
to={linkToMonth(monthYearFromDate(nextMonth(activeMonth)))}
title={translatedMonthFormat.format(nextMonth(activeMonth))}
onClick={() => setIsLoading(true)}
>
{shortTranslatedMonthFormat.format(nextMonth(activeMonth))}
<ChevronRightIcon className="inline h-6" />
</Link>
</div>

{useNativeMonthPicker ? null : (
<MonthPicker
open={showMonthPicker}
setOpen={setShowMonthPicker}
activeMonth={activeMonth}
route={route}
/>
)}
</div>
)
}

export default MonthSlider

0 comments on commit bf1c5be

Please sign in to comment.