Build a Finance SaaS Platform -26 (Account/Date Filter)(End)

Install Package

1
npm i query-string

Add Filter

Add components/filters.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
import { AccountFilter } from '@/components/account-filter'
import { DateFilter } from '@/components/date-filter'

export const Filters = () => {
return (
<div className="flex flex-col lg:flex-row items-center gap-y-2 lg:gap-y-0 lg:gap-x-2">
<AccountFilter />
<DateFilter />
</div>
)
}

Add Account Filter

Add components/account-filter.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"use client"

import qs from "query-string";
import {
usePathname,
useRouter,
useSearchParams
} from "next/navigation";

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

import { useGetAccounts } from "@/features/accounts/api/use-get-accounts";
import { useGetSummary } from "@/features/summary/api/use-get-summary";

export const AccountFilter = () => {
const router = useRouter();
const pathname = usePathname();

const params = useSearchParams();
const accountId = params.get("accountId") || "all";
const from = params.get("from") || "";
const to = params.get("to") || "";

const { isLoading: isLoadingSummary } = useGetSummary();
const {
data: accounts,
isLoading: isLoadingAccounts
} = useGetAccounts();

const onChange = ( newValue: string) => {
const query = {
accountId: newValue,
from,
to,
};

if (newValue === "all") {
query.accountId = "";
}

const url = qs.stringifyUrl({
url: pathname,
query,
}, { skipNull:true, skipEmptyString: true});

router.push(url);
};

return (
<Select
value={accountId}
onValueChange={onChange}
disabled={isLoadingAccounts || isLoadingSummary }
>
<SelectTrigger
className="lg:w-auto w-full h-9 rounded-md px-3 font-normal bg-white/10 hover:bg-white/20 hover:text-white border-none focus:ring-offset-0 focus:ring-transparent outline-none text-white focus:bg-white/30 transition"
>
<SelectValue placeholder="Account" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
All accounts
</SelectItem>
{accounts?.map((account) =>(
<SelectItem
key={account.id}
value={account.id}
>
{ account.name }
</SelectItem>
))}
</SelectContent>
</Select>
)
}

Add Date Filter

Add components/date-filter.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"use client"

import { useState } from "react";
import { format, subDays } from "date-fns";
import { DateRange } from "react-day-picker";
import { ChevronDown } from "lucide-react";
import qs from "query-string";

import {
usePathname,
useRouter,
useSearchParams
} from "next/navigation";

import {
Popover,
PopoverContent,
PopoverTrigger,
PopoverClose,
} from "@/components/ui/popover";

import { useGetSummary } from "@/features/summary/api/use-get-summary";
import { Button } from "./ui/button";
import { formatDateRange } from "@/lib/utils";
import { Calendar } from "./ui/calendar";

export const DateFilter = () => {
const router = useRouter();
const pathname = usePathname();

const params = useSearchParams();
const accountId = params.get("accountId");
const from = params.get("from") || "";
const to = params.get("to") || "";

const defaultTo = new Date();
const defaultFrom = subDays(defaultTo, 30);

const paramState = {
from: from ? new Date(from) : defaultFrom,
to: to ? new Date(to) : defaultTo,
};

const [selectedDate, setSelectedDate] = useState<DateRange | undefined>(paramState);

const pushToUrl = ( dateRange: DateRange | undefined) => {
const query = {
from: format(dateRange?.from || defaultFrom, "yyyy-MM-dd"),
to: format(dateRange?.to || defaultTo, "yyyy-MM-dd"),
accountId,
}

const url = qs.stringifyUrl({
url: pathname,
query,
}, { skipEmptyString:true, skipNull:true });

router.push(url);
};

const onReset = () => {
setSelectedDate(undefined);
pushToUrl(undefined);
};

return (
<Popover>
<PopoverTrigger asChild>
<Button
disabled={false}
size="sm"
variant="outline"
className="lg:w-auto w-full h-9 rounded-md px-3 font-normal bg-white/10 hover:bg-white/20 hover:text-white border-none focus:ring-offset-0 focus:ring-transparent outline-none text-white focus:bg-white/30 transition"
>
<span>{formatDateRange(paramState)}</span>
<ChevronDown className="ml-2 size-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="lg:w-auto w-full p-0"
align="start"
>
<Calendar
disabled={false}
initialFocus
mode="range"
defaultMonth={selectedDate?.from}
selected={selectedDate}
onSelect={setSelectedDate}
numberOfMonths={2}
/>
<div className="p-4 w-full flex items-center gap-x-2">
<PopoverClose asChild>
<Button
onClick={onReset}
disabled={!selectedDate?.from || !selectedDate?.to }
className="w-full"
variant="outline"
>
Reset
</Button>
</PopoverClose>
<PopoverClose asChild>
<Button
onClick={()=> pushToUrl(selectedDate)}
disabled={!selectedDate?.from || !selectedDate?.to }
className="w-full"
>
Apply
</Button>
</PopoverClose>
</div>
</PopoverContent>
</Popover>
)
}

Modify Header Component

Modify components/header.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from 'react'
import { ClerkLoaded, ClerkLoading, UserButton } from '@clerk/nextjs'
import { Loader2 } from 'lucide-react'

import HeaderLogo from '@/components/header-logo'
import Navigation from '@/components/navigation'
import WelcomeMsg from '@/components/welcome-msg'
import { Filters } from '@/components/filters'

export default function Header() {
return (
<div className='bg-gradient-to-b from-blue-700 to-blue-500 px-4 py-8 lg:px-14 pb-36'>
<div className='max-w-screen-2xl mx-auto'>
<div className='w-full flex items-center justify-between mb-14'>
<div className='flex items-center lg:gap-x-16'>
<HeaderLogo />
<Navigation />
</div>
<ClerkLoaded>
<UserButton />
</ClerkLoaded>
<ClerkLoading>
<Loader2 className='size-8 animate-spin text-slate-400' />
</ClerkLoading>
</div>
<WelcomeMsg />
<Filters />
</div>
</div>
)
}

Modify Popover Componennt

Modify components/ui/popover.tsx

1
2
3
4
...
const PopoverClose = PopoverPrimitive.Close;
...
export { Popover, PopoverTrigger, PopoverContent,PopoverClose }

请我喝杯咖啡吧~

支付宝
微信