Skip to content

Commit

Permalink
feat: add filter by purchase date
Browse files Browse the repository at this point in the history
  • Loading branch information
ainunns committed Dec 1, 2024
1 parent 7509d00 commit 4de7880
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 71 deletions.
59 changes: 59 additions & 0 deletions resources/js/Components/DatepickerFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from "react";
import { FormProvider, useForm, useWatch } from "react-hook-form";
import RangeDatePicker from "./Forms/RangeDatepicker";

export type PopupFilterProps = {
date: Date[];
setDate: React.Dispatch<React.SetStateAction<Date[]>>;
onResetFilter?: () => void;
} & React.ComponentPropsWithoutRef<"div">;

type FormData = {
filter: Date[];
};

export default function DatepickerFilter({
date,
setDate,
onResetFilter,
}: PopupFilterProps) {
//#region //*=========== Form ===========t
const methods = useForm<FormData>({
mode: "onTouched",
defaultValues: {
filter: date,
},
});
const { control, setValue } = methods;

const filter =
useWatch({
control,
name: "filter",
}) ?? [];
//#endregion //*======== Form ===========

React.useEffect(() => {
setDate(filter);
}, [filter]);

const resetFilter = () => {
onResetFilter?.();
setValue("filter", []);
};

return (
<FormProvider {...methods}>
<div className="relative w-80">
<RangeDatePicker
id="filter"
label={null}
placeholder="Filter by date"
containerClassName="w-full"
isClearable={filter.length > 0}
onClearDate={resetFilter}
/>
</div>
</FormProvider>
);
}
22 changes: 13 additions & 9 deletions resources/js/Components/Forms/RangeDatepicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import clsx from "clsx";
import get from "lodash.get";
import { Calendar } from "lucide-react";
import { Calendar, XCircle } from "lucide-react";
import { Controller, RegisterOptions, useFormContext } from "react-hook-form";

import "react-datepicker/dist/react-datepicker.css";
Expand All @@ -16,12 +16,12 @@ type ReactDatePickerProps = {
placeholder?: string;
defaultYear?: number;
defaultMonth?: number;
defaultValue?: string;
helperText?: string;
readOnly?: boolean;
/** Disable error style (not disabling error validation) */
hideError?: boolean;
containerClassName?: string;
onClearDate?: () => void;
} & Omit<DatePickerProps, "onChange">;

export default function RangeDatePicker({
Expand All @@ -31,11 +31,12 @@ export default function RangeDatePicker({
placeholder,
defaultYear,
defaultMonth,
defaultValue,
helperText,
readOnly = false,
hideError = false,
disabled,
isClearable,
onClearDate,
containerClassName,
}: ReactDatePickerProps) {
const {
Expand Down Expand Up @@ -76,7 +77,6 @@ export default function RangeDatePicker({
<Controller
control={control}
rules={validation}
defaultValue={defaultValue}
name={id}
render={({ field: { onChange, onBlur, value } }) => {
const startDate = Array.isArray(value)
Expand All @@ -100,7 +100,7 @@ export default function RangeDatePicker({
endDate={endDate}
className={clsx(
"flex w-full rounded-lg shadow-sm",
"min-h-[2.25rem] py-0 md:min-h-[2.5rem]",
"min-h-[2.5rem] py-0 md:min-h-[2.75rem]",
"border-gray-300 focus:border-primary-500 focus:ring-primary-500",
(readOnly || disabled) &&
"cursor-not-allowed border-gray-300 bg-gray-100 focus:border-gray-300 focus:ring-0",
Expand All @@ -118,10 +118,14 @@ export default function RangeDatePicker({
disabled={disabled}
selectsRange
/>
<Calendar
size={18}
className="pointer-events-none absolute right-4 top-1/2 -translate-y-1/2 transform text-typo-icons"
/>
<div className="absolute flex gap-2 right-4 top-1/2 -translate-y-1/2 transform text-typo-icons">
{isClearable && (
<button type="button" onClick={() => onClearDate?.()}>
<XCircle size={16} className="text-typo-icons" />
</button>
)}
<Calendar size={18} />
</div>
</div>
{helperText && (
<Typography variant="c1" color="secondary" className="mt-1">
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion resources/js/Pages/Product/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Link, usePage } from "@inertiajs/react";
import { Head } from "@inertiajs/react";
import { MapPin, Plus, Search, XCircle } from "lucide-react";
import * as React from "react";
import PopupFilter, { PopupFilterProps } from "./Components/PopupFilter";
import PopupFilter, { PopupFilterProps } from "../../Components/PopupFilter";

type CategoryFilter = {
category: string[];
Expand Down
145 changes: 85 additions & 60 deletions resources/js/Pages/Transaction/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
AccordionTrigger,
} from "@/Components/Accordion";
import Badge from "@/Components/Badge";
import DatepickerFilter from "@/Components/DatepickerFilter";
import PopupFilter, { PopupFilterProps } from "@/Components/PopupFilter";
import Typography from "@/Components/Typography";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { cn, numberToCurrency } from "@/Lib/utils";
Expand All @@ -13,12 +15,9 @@ import { CategoryType } from "@/types/entities/category";
import { TransactionType } from "@/types/entities/transaction";
import { Head } from "@inertiajs/react";
import { router } from "@inertiajs/react";
import { format } from "date-fns";
import { format, formatDate } from "date-fns";
import { Search, XCircle } from "lucide-react";
import * as React from "react";
import PopupFilter, {
PopupFilterProps,
} from "../Product/Components/PopupFilter";
import NotFound from "./container/NotFound";

type CategoryFilter = {
Expand All @@ -29,20 +28,36 @@ type TransactionIndexProps = {
transaction: TransactionType[];
category: CategoryType[];
categoryIds: string[];
startDate: string;
endDate: string;
};

type QueryType = {
category: string[];
start_date?: string;
end_date?: string;
};

export default function TransactionIndex({
transaction,
category,
categoryIds,
startDate,
endDate,
}: TransactionIndexProps) {
const [filter, setFilter] = React.useState<string>("");

const [filterDate, setFilterDate] = React.useState<Date[]>(
[
startDate ? new Date(startDate) : null,
endDate ? new Date(endDate) : null,
].filter(Boolean) as Date[],
);
const [filterQuery, setFilterQuery] = React.useState<CategoryFilter>({
category: categoryIds,
});

const prevCategory = React.useRef<string[]>(categoryIds);
const prevDate = React.useRef<Date[]>(filterDate);

const filterOption: PopupFilterProps<CategoryFilter>["filterOption"] =
React.useMemo(
Expand Down Expand Up @@ -79,68 +94,78 @@ export default function TransactionIndex({
React.useEffect(() => {
if (
JSON.stringify(filterQuery.category) !==
JSON.stringify(prevCategory.current)
JSON.stringify(prevCategory.current) ||
(JSON.stringify(filterDate) !== JSON.stringify(prevDate.current) &&
!(!filterDate[0] !== !filterDate[1]))
) {
router.get(
"/history",
{ category_id: filterQuery.category },
{
preserveState: true,
preserveScroll: true,
},
);
const query: QueryType = {
category: filterQuery.category,
};
if (filterDate[0] && filterDate[1]) {
query.start_date = formatDate(filterDate[0], "yyyy-MM-dd");
query.end_date = formatDate(filterDate[1], "yyyy-MM-dd");
}
router.get("/history", query, {
preserveState: true,
preserveScroll: true,
});
prevCategory.current = filterQuery.category;
prevDate.current = filterDate;
}
}, [filterQuery.category]);
}, [filterQuery.category, filterDate]);

return (
<AuthenticatedLayout>
<Head title="Order List" />
{transaction.length === 0 ? (
<NotFound />
) : (
<section className="px-10 md:px-20 py-8 flex flex-col gap-4">
<Typography variant="h1" className="font-semibold">
Order List
</Typography>

<div className="flex flex-col md:flex-row justify-between mt-6 gap-y-4">
<div className="relative mt-1 self-start w-full md:w-fit">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<Search className="text-xl text-typo" />
</div>
<input
type="text"
value={filter ?? ""}
onChange={handleFilterChange}
className={cn(
"flex w-full rounded-lg shadow-sm",
"min-h-[2.25rem] py-0 px-10 md:min-h-[2.5rem]",
"border-gray-300 focus:border-primary-500 focus:ring-primary-500",
)}
placeholder="Search..."
/>
{filter !== "" && (
<div className="absolute inset-y-0 right-0 flex items-center pr-2">
<button
type="button"
onClick={handleClearFilter}
className="p-1"
>
<XCircle className="text-xl text-typo-icons" />
</button>
</div>
)}
</div>
<div className="flex gap-2">
<PopupFilter
filterQuery={filterQuery}
filterOption={filterOption}
setFilterQuery={setFilterQuery}
onResetFilter={() => setFilterQuery({ category: [] })}
/>
<section className="px-10 md:px-20 py-8 flex flex-col gap-4">
<Typography variant="h1" className="font-semibold">
Order List
</Typography>
<div className="flex flex-col md:flex-row justify-between mt-6 gap-y-4">
<div className="relative mt-1 self-start w-full md:w-fit">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<Search className="text-xl text-typo" />
</div>
<input
type="text"
value={filter ?? ""}
onChange={handleFilterChange}
className={cn(
"flex w-full rounded-lg shadow-sm",
"min-h-[2.25rem] py-0 px-10 md:min-h-[2.5rem]",
"border-gray-300 focus:border-primary-500 focus:ring-primary-500",
)}
placeholder="Search..."
/>
{filter !== "" && (
<div className="absolute inset-y-0 right-0 flex items-center pr-2">
<button
type="button"
onClick={handleClearFilter}
className="p-1"
>
<XCircle className="text-xl text-typo-icons" />
</button>
</div>
)}
</div>
<div className="flex gap-2">
<DatepickerFilter
date={filterDate}
setDate={setFilterDate}
onResetFilter={() => setFilterDate([])}
/>
<PopupFilter
filterQuery={filterQuery}
filterOption={filterOption}
setFilterQuery={setFilterQuery}
onResetFilter={() => setFilterQuery({ category: [] })}
/>
</div>
</div>
{filteredTransaction.length === 0 ? (
<NotFound />
) : (
<div className="flex flex-col w-full gap-4">
{filteredTransaction.map((t) => (
<div
Expand Down Expand Up @@ -215,8 +240,8 @@ export default function TransactionIndex({
</div>
))}
</div>
</section>
)}
)}
</section>
</AuthenticatedLayout>
);
}
Expand Down
2 changes: 1 addition & 1 deletion resources/js/Pages/Transaction/container/NotFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Typography from "@/Components/Typography";

export default function NotFound() {
return (
<section className="w-full min-h-screen flex flex-col items-center justify-center gap-6">
<section className="w-full py-40 flex flex-col items-center justify-center gap-6">
<svg
width="214"
height="211"
Expand Down

0 comments on commit 4de7880

Please sign in to comment.