From c5b52223037a62069beb12d7ad783a6eb911fe72 Mon Sep 17 00:00:00 2001 From: nisrinasalm Date: Fri, 22 Nov 2024 16:43:39 +0700 Subject: [PATCH 01/13] feat: add history transaction query --- .../Controllers/TransactionController.php | 24 +++++++++++++++++++ resources/js/Pages/Transaction/Index.tsx | 3 +++ resources/js/types/entities/transaction.ts | 8 +++++++ routes/web.php | 12 ++++++---- 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 app/Http/Controllers/TransactionController.php create mode 100644 resources/js/Pages/Transaction/Index.tsx create mode 100644 resources/js/types/entities/transaction.ts diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php new file mode 100644 index 0000000..09c2805 --- /dev/null +++ b/app/Http/Controllers/TransactionController.php @@ -0,0 +1,24 @@ +whereHas('cart_product', function ($query) { + $query->where('user_id', Auth::id()); + }) + ->orderBy('created_at', 'desc') + ->get(); + + return Inertia::render('Transaction/Index', [ + 'transaction' => $transaction + ]); + } +} diff --git a/resources/js/Pages/Transaction/Index.tsx b/resources/js/Pages/Transaction/Index.tsx new file mode 100644 index 0000000..9084a48 --- /dev/null +++ b/resources/js/Pages/Transaction/Index.tsx @@ -0,0 +1,3 @@ +export default function Transaction() { + return <>; +} diff --git a/resources/js/types/entities/transaction.ts b/resources/js/types/entities/transaction.ts new file mode 100644 index 0000000..a96b313 --- /dev/null +++ b/resources/js/types/entities/transaction.ts @@ -0,0 +1,8 @@ +import { CartType } from "./cart"; + +export type TransactionType = { + id: string; + total_price: number; + cart_product: CartType[]; + created_at: Date; +}; diff --git a/routes/web.php b/routes/web.php index 1b50b57..edb2f20 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,7 +3,7 @@ use App\Http\Controllers\CartController; use App\Http\Controllers\ProductController; use App\Http\Controllers\ProfileController; -use Illuminate\Foundation\Application; +use App\Http\Controllers\TransactionController; use Illuminate\Support\Facades\Route; use Inertia\Inertia; use Illuminate\Support\Facades\Auth; @@ -20,7 +20,7 @@ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, - 'role' => $user->role, + 'role' => $user->role, ] : null, ], ]); @@ -31,7 +31,7 @@ Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); - + Route::prefix('/product')->group(function () { Route::get('/', [ProductController::class, 'index'])->name('product.index'); Route::get('/{product}/detail', [ProductController::class, 'show'])->name('product.show'); @@ -39,12 +39,14 @@ }); Route::middleware('auth', 'role:user')->group(function () { - Route::prefix('/cart')->group(function() { + Route::prefix('/cart')->group(function () { Route::get('/', [CartController::class, 'viewCart'])->name('cart.index'); Route::post('/add/{product}', [CartController::class, 'addToCart'])->name('cart.add'); Route::delete('/{id}/delete', [CartController::class, 'deleteFromCart'])->name('cart.delete'); Route::post('/checkout', [CartController::class, 'checkout'])->name('cart.checkout'); }); + + Route::get('/transaction', [TransactionController::class, 'index'])->name('transaction.index'); }); Route::middleware('auth', 'role:admin')->group(function () { @@ -57,4 +59,4 @@ }); }); -require __DIR__.'/auth.php'; +require __DIR__ . '/auth.php'; From fb33ed5970d8e2d03fd8639f198258fa76e995e0 Mon Sep 17 00:00:00 2001 From: nisrinasalm Date: Fri, 22 Nov 2024 16:49:34 +0700 Subject: [PATCH 02/13] feat: add transaction to navbar --- resources/js/Layouts/Navbar.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resources/js/Layouts/Navbar.tsx b/resources/js/Layouts/Navbar.tsx index 1dbcc68..79ceeb7 100644 --- a/resources/js/Layouts/Navbar.tsx +++ b/resources/js/Layouts/Navbar.tsx @@ -44,6 +44,14 @@ export default function Navbar() { > Cart + )}{" "} + {auth.user !== null && auth.user.role === "user" && ( + + Transaction + )} From f000c904cd1135deab2475b0133b42246f86cdc7 Mon Sep 17 00:00:00 2001 From: nisrinasalm Date: Fri, 22 Nov 2024 16:56:43 +0700 Subject: [PATCH 03/13] feat: change /transaction to /history --- routes/web.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/web.php b/routes/web.php index edb2f20..a4be13d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -46,7 +46,7 @@ Route::post('/checkout', [CartController::class, 'checkout'])->name('cart.checkout'); }); - Route::get('/transaction', [TransactionController::class, 'index'])->name('transaction.index'); + Route::get('/history', [TransactionController::class, 'index'])->name('transaction.index'); }); Route::middleware('auth', 'role:admin')->group(function () { From 6c3f3b014f7f468bf758392cd9ea4f9b0f42e6cc Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Sat, 23 Nov 2024 10:39:05 +0700 Subject: [PATCH 04/13] feat: update checkout redirect and enhance transaction index page --- app/Http/Controllers/CartController.php | 2 +- resources/js/Components/UnstyledLink.tsx | 9 ++++++++- resources/js/Pages/Product/Index.tsx | 8 ++++---- resources/js/Pages/Product/Show.tsx | 2 +- resources/js/Pages/Transaction/Index.tsx | 15 +++++++++++++-- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/CartController.php b/app/Http/Controllers/CartController.php index 3d58ca4..3409c51 100644 --- a/app/Http/Controllers/CartController.php +++ b/app/Http/Controllers/CartController.php @@ -105,7 +105,7 @@ function checkout(Request $request) DB::commit(); - return redirect()->back(); + return redirect()->intended(route('transaction.index')); } catch (\Exception $e) { DB::rollBack(); throw $e; diff --git a/resources/js/Components/UnstyledLink.tsx b/resources/js/Components/UnstyledLink.tsx index 54f0c8d..ddf649f 100644 --- a/resources/js/Components/UnstyledLink.tsx +++ b/resources/js/Components/UnstyledLink.tsx @@ -12,7 +12,14 @@ export type UnstyledLinkProps = { const UnstyledLink = React.forwardRef( ( - { children, href, openNewTab, className, inertiaLinkProps, ...rest }, + { + children, + href, + openNewTab = false, + className, + inertiaLinkProps, + ...rest + }, ref, ) => { const isNewTab = diff --git a/resources/js/Pages/Product/Index.tsx b/resources/js/Pages/Product/Index.tsx index aa95c6c..95c16fc 100644 --- a/resources/js/Pages/Product/Index.tsx +++ b/resources/js/Pages/Product/Index.tsx @@ -65,8 +65,8 @@ const ProductIndex = ({ All Product -
-
+
+
@@ -106,12 +106,12 @@ const ProductIndex = ({ openNewTab={false} leftIcon={Plus} > - Add Product + Product )}
-
+
{productList.map((p) => (
-
+
; +import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; +import { TransactionType } from "@/types/entities/transaction"; +import { Head } from "@inertiajs/react"; + +export default function TransactionIndex({ + transaction, +}: { transaction: TransactionType[] }) { + return ( + + +
{JSON.stringify(transaction, null, 2)}
+
+ ); } From e308764fa6df58ed33ac97273e3ad4b231bf4713 Mon Sep 17 00:00:00 2001 From: nisrinasalm Date: Sat, 23 Nov 2024 14:50:06 +0700 Subject: [PATCH 05/13] feat: add filter by category and date in TransactionController --- .../Controllers/TransactionController.php | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 09c2805..f0c2fce 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -3,22 +3,47 @@ namespace App\Http\Controllers; use App\Models\Transaction; +use App\Models\Category; use Illuminate\Support\Facades\Auth; use Inertia\Inertia; +use Illuminate\Http\Request; +use Illuminate\Support\Carbon; class TransactionController extends Controller { - public function index() + public function index(Request $request) { - $transaction = Transaction::with(['cart_product.product']) + $categoryId = $request->query('category_id'); + $startDate = $request->query('start_date'); + $endDate = $request->query('end_date'); + + $category = Category::all(); + + $transaction = Transaction::with(['cart_product.product.category']) + ->whereHas('cart_product.product.category', function ($query) use ($categoryId) { + if ($categoryId) { + $query->where('category_id', $categoryId); + } + }) ->whereHas('cart_product', function ($query) { $query->where('user_id', Auth::id()); - }) - ->orderBy('created_at', 'desc') - ->get(); + }); + + if ($startDate && $endDate) { + $transaction = $transaction->whereBetween('created_at', [ + Carbon::parse($startDate)->startOfDay(), + Carbon::parse($endDate)->endOfDay() + ]); + } + + $transaction = $transaction->orderBy('created_at', 'desc')->get(); return Inertia::render('Transaction/Index', [ - 'transaction' => $transaction + 'transaction' => $transaction, + 'category' => $category, + 'categoryId' => $categoryId, + 'startDate' => $startDate, + 'endDate' => $endDate, ]); } } From 99a5bea6db3f5307c98d22c236ac2a03aa15dbb0 Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Thu, 28 Nov 2024 01:24:26 +0700 Subject: [PATCH 06/13] feat: add history transaction page --- package.json | 1 + pnpm-lock.yaml | 8 ++ resources/js/Components/Accordion.tsx | 2 +- resources/js/Pages/Transaction/Index.tsx | 133 ++++++++++++++++++++++- 4 files changed, 142 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 750ab52..5301b80 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-popover": "^1.1.2", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "lodash.get": "^4.4.2", "lucide-react": "^0.451.0", "react-datepicker": "^7.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c7f638..6053135 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 lodash.get: specifier: ^4.4.2 version: 4.4.2 @@ -1263,6 +1266,9 @@ packages: date-fns@3.6.0: resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -3468,6 +3474,8 @@ snapshots: date-fns@3.6.0: {} + date-fns@4.1.0: {} + debug@4.3.7: dependencies: ms: 2.1.3 diff --git a/resources/js/Components/Accordion.tsx b/resources/js/Components/Accordion.tsx index 720cfa9..12ed2a3 100644 --- a/resources/js/Components/Accordion.tsx +++ b/resources/js/Components/Accordion.tsx @@ -24,7 +24,7 @@ const AccordionTrigger = React.forwardRef< -
{JSON.stringify(transaction, null, 2)}
+ {transaction.length === 0 ? ( + <>beli dulu sana + ) : ( +
+ + Order List + +
+ {transaction.map((t) => ( +
+
+ + {format(new Date(t.created_at), "dd MMMM yyyy")} + + + | + + + TR/{format(new Date(t.created_at), "yyyyMMdd")}/{t.id} + +
+
+ + {t.cart_product.length > 1 && ( + + + + {t.cart_product.map( + (cp, index) => + index !== 0 && ( + + ), + )} + + + + Show more items + + + + + Show less items + + + + + )} +
+
+
+ + Grand Total: + + + {numberToCurrency(t.total_price)} + +
+
+
+
+ ))} +
+
+ )} ); } + +type ProductItemsProps = { + cart_product: CartType; + quantity: number; +}; + +function ProductItems({ cart_product: cp, quantity }: ProductItemsProps) { + return ( +
+
+ {cp.product.name} +
+ + {cp.product.name} + + + {quantity} item{quantity > 1 && "s"} x{" "} + {numberToCurrency(cp.product.price)} + +
+
+
+ + Total Price + + + {numberToCurrency(cp.product.price * cp.quantity)} + +
+
+ ); +} From 26284cf1e772c83b071158ab45ba468ac61a072c Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Thu, 28 Nov 2024 01:40:57 +0700 Subject: [PATCH 07/13] feat: add empty transaction state --- resources/js/Pages/Transaction/Index.tsx | 3 +- .../Pages/Transaction/container/NotFound.tsx | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 resources/js/Pages/Transaction/container/NotFound.tsx diff --git a/resources/js/Pages/Transaction/Index.tsx b/resources/js/Pages/Transaction/Index.tsx index 3f9feaf..160bc0a 100644 --- a/resources/js/Pages/Transaction/Index.tsx +++ b/resources/js/Pages/Transaction/Index.tsx @@ -11,6 +11,7 @@ import { CartType } from "@/types/entities/cart"; import { TransactionType } from "@/types/entities/transaction"; import { Head } from "@inertiajs/react"; import { format } from "date-fns"; +import NotFound from "./container/NotFound"; export default function TransactionIndex({ transaction, @@ -19,7 +20,7 @@ export default function TransactionIndex({ {transaction.length === 0 ? ( - <>beli dulu sana + ) : (
diff --git a/resources/js/Pages/Transaction/container/NotFound.tsx b/resources/js/Pages/Transaction/container/NotFound.tsx new file mode 100644 index 0000000..f29c5c1 --- /dev/null +++ b/resources/js/Pages/Transaction/container/NotFound.tsx @@ -0,0 +1,62 @@ +import ButtonLink from "@/Components/ButtonLink"; +import Typography from "@/Components/Typography"; + +export default function NotFound() { + return ( +
+ + + + + + + + + + + +
+ + No Orders Yet + + + Your order history is looking a bit empty. Start exploring our amazing + products and make your first purchase! + +
+ + Shop Now + +
+ ); +} From 7008382e4e95a862d4b0ea28ca05f4738d0726e0 Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Thu, 28 Nov 2024 01:52:21 +0700 Subject: [PATCH 08/13] feat: add search filter to transaction index page --- resources/js/Pages/Transaction/Index.tsx | 51 ++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/resources/js/Pages/Transaction/Index.tsx b/resources/js/Pages/Transaction/Index.tsx index 160bc0a..f66e590 100644 --- a/resources/js/Pages/Transaction/Index.tsx +++ b/resources/js/Pages/Transaction/Index.tsx @@ -6,16 +6,27 @@ import { } from "@/Components/Accordion"; import Typography from "@/Components/Typography"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; -import { numberToCurrency } from "@/Lib/utils"; +import { cn, numberToCurrency } from "@/Lib/utils"; import { CartType } from "@/types/entities/cart"; import { TransactionType } from "@/types/entities/transaction"; import { Head } from "@inertiajs/react"; import { format } from "date-fns"; +import { Search, XCircle } from "lucide-react"; +import * as React from "react"; import NotFound from "./container/NotFound"; export default function TransactionIndex({ transaction, }: { transaction: TransactionType[] }) { + const [filter, setFilter] = React.useState(""); + + const filteredTransaction = transaction.filter((t) => { + const matchesText = t.cart_product.some((cp) => + cp.product.name.toLowerCase().includes(filter.toLowerCase()), + ); + return matchesText; + }); + return ( @@ -26,8 +37,40 @@ export default function TransactionIndex({ Order List + +
+
+
+ +
+ { + setFilter(String(e.target.value)); + }} + 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 !== "" && ( +
+ +
+ )} +
+
- {transaction.map((t) => ( + {filteredTransaction.map((t) => (
Show more items @@ -77,7 +120,7 @@ export default function TransactionIndex({ Show less items From 67b74a31e479c7e72478539b40ca17a5e9cbfedc Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Thu, 28 Nov 2024 02:03:56 +0700 Subject: [PATCH 09/13] feat: update category filter to support multiple category --- app/Http/Controllers/TransactionController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index f0c2fce..70c0849 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -13,16 +13,16 @@ class TransactionController extends Controller { public function index(Request $request) { - $categoryId = $request->query('category_id'); + $categoryIds = $request->query('category_id', []); $startDate = $request->query('start_date'); $endDate = $request->query('end_date'); $category = Category::all(); $transaction = Transaction::with(['cart_product.product.category']) - ->whereHas('cart_product.product.category', function ($query) use ($categoryId) { - if ($categoryId) { - $query->where('category_id', $categoryId); + ->whereHas('cart_product.product.category', function ($query) use ($categoryIds) { + if (!empty($categoryIds)) { + $query->whereIn('category_id', $categoryIds); } }) ->whereHas('cart_product', function ($query) { @@ -41,7 +41,7 @@ public function index(Request $request) return Inertia::render('Transaction/Index', [ 'transaction' => $transaction, 'category' => $category, - 'categoryId' => $categoryId, + 'categoryIds' => $categoryIds, 'startDate' => $startDate, 'endDate' => $endDate, ]); From 7509d00bc30a9f3fcf24bc501b3ea321b30610a8 Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Sun, 1 Dec 2024 18:39:31 +0700 Subject: [PATCH 10/13] feat: add filter transaction by category --- resources/js/Components/Badge.tsx | 7 +- .../Pages/Product/Components/PopupFilter.tsx | 33 ++++-- resources/js/Pages/Product/Index.tsx | 1 + resources/js/Pages/Transaction/Index.tsx | 101 ++++++++++++++++-- 4 files changed, 123 insertions(+), 19 deletions(-) diff --git a/resources/js/Components/Badge.tsx b/resources/js/Components/Badge.tsx index 0e5da25..7793061 100644 --- a/resources/js/Components/Badge.tsx +++ b/resources/js/Components/Badge.tsx @@ -1,10 +1,13 @@ import * as React from "react"; import Typography from "./Typography"; -export default function Badge({ children }: { children: React.ReactNode }) { +export default function Badge({ + children, + className, +}: { children: React.ReactNode; className?: string }) { return (
- + {children}
diff --git a/resources/js/Pages/Product/Components/PopupFilter.tsx b/resources/js/Pages/Product/Components/PopupFilter.tsx index a56b00d..a1a73e9 100644 --- a/resources/js/Pages/Product/Components/PopupFilter.tsx +++ b/resources/js/Pages/Product/Components/PopupFilter.tsx @@ -20,25 +20,43 @@ export type PopupFilterProps> = { name: string; }[]; }[]; + filterQuery: T; setFilterQuery: React.Dispatch>; + onResetFilter?: () => void; title?: string; } & React.ComponentPropsWithoutRef<"div">; +type FormData = { + filter: string[]; +}; + export default function PopupFilter>({ filterOption, + filterQuery, setFilterQuery, + onResetFilter, title = "Filter", }: PopupFilterProps) { //#region //*=========== Form =========== - const methods = useForm({ + const defaultFilterValues = React.useMemo(() => { + return Object.entries(filterQuery).reduce((acc, [key, values]) => { + return [...acc, ...values.map((value) => `${key}.${value}`)]; + }, [] as string[]); + }, [filterQuery]); + + const methods = useForm({ mode: "onTouched", + defaultValues: { + filter: defaultFilterValues, + }, }); const { control, setValue } = methods; - const filter: string[] = useWatch({ - control, - name: "filter[]", - }); + const filter = + useWatch({ + control, + name: "filter", + }) ?? []; //#endregion //*======== Form =========== React.useEffect(() => { @@ -56,7 +74,10 @@ export default function PopupFilter>({ setFilterQuery(parsedFilter); }, [filter, filterOption, setFilterQuery]); - const resetFilter = () => setValue("filter[]", []); + const resetFilter = () => { + onResetFilter?.(); + setValue("filter", []); + }; return ( diff --git a/resources/js/Pages/Product/Index.tsx b/resources/js/Pages/Product/Index.tsx index 95c16fc..452f218 100644 --- a/resources/js/Pages/Product/Index.tsx +++ b/resources/js/Pages/Product/Index.tsx @@ -97,6 +97,7 @@ const ProductIndex = ({
diff --git a/resources/js/Pages/Transaction/Index.tsx b/resources/js/Pages/Transaction/Index.tsx index f66e590..1f61a86 100644 --- a/resources/js/Pages/Transaction/Index.tsx +++ b/resources/js/Pages/Transaction/Index.tsx @@ -4,29 +4,95 @@ import { AccordionItem, AccordionTrigger, } from "@/Components/Accordion"; +import Badge from "@/Components/Badge"; import Typography from "@/Components/Typography"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import { cn, numberToCurrency } from "@/Lib/utils"; import { CartType } from "@/types/entities/cart"; +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 { Search, XCircle } from "lucide-react"; import * as React from "react"; +import PopupFilter, { + PopupFilterProps, +} from "../Product/Components/PopupFilter"; import NotFound from "./container/NotFound"; +type CategoryFilter = { + category: string[]; +}; + +type TransactionIndexProps = { + transaction: TransactionType[]; + category: CategoryType[]; + categoryIds: string[]; +}; + export default function TransactionIndex({ transaction, -}: { transaction: TransactionType[] }) { + category, + categoryIds, +}: TransactionIndexProps) { const [filter, setFilter] = React.useState(""); - const filteredTransaction = transaction.filter((t) => { - const matchesText = t.cart_product.some((cp) => - cp.product.name.toLowerCase().includes(filter.toLowerCase()), - ); - return matchesText; + const [filterQuery, setFilterQuery] = React.useState({ + category: categoryIds, }); + const prevCategory = React.useRef(categoryIds); + + const filterOption: PopupFilterProps["filterOption"] = + React.useMemo( + () => [ + { + id: "category", + name: "Category", + options: category, + }, + ], + [category], + ); + + const filteredTransaction = React.useMemo(() => { + return transaction.filter((t) => { + const matchesText = t.cart_product.some((cp) => + cp.product.name.toLowerCase().includes(filter.toLowerCase()), + ); + return matchesText; + }); + }, [transaction, filter]); + + const handleFilterChange = React.useCallback( + (e: React.ChangeEvent) => { + setFilter(String(e.target.value)); + }, + [], + ); + + const handleClearFilter = React.useCallback(() => { + setFilter(""); + }, []); + + React.useEffect(() => { + if ( + JSON.stringify(filterQuery.category) !== + JSON.stringify(prevCategory.current) + ) { + router.get( + "/history", + { category_id: filterQuery.category }, + { + preserveState: true, + preserveScroll: true, + }, + ); + prevCategory.current = filterQuery.category; + } + }, [filterQuery.category]); + return ( @@ -46,9 +112,7 @@ export default function TransactionIndex({ { - setFilter(String(e.target.value)); - }} + onChange={handleFilterChange} className={cn( "flex w-full rounded-lg shadow-sm", "min-h-[2.25rem] py-0 px-10 md:min-h-[2.5rem]", @@ -60,7 +124,7 @@ export default function TransactionIndex({
)}
+
+ setFilterQuery({ category: [] })} + /> +
{filteredTransaction.map((t) => ( @@ -166,10 +238,17 @@ function ProductItems({ cart_product: cp, quantity }: ProductItemsProps) { alt={cp.product.name} className="size-20 object-fit rounded" /> -
+
{cp.product.name} +
+ {cp.product.category.map((c) => ( + + {c.name} + + ))} +
{quantity} item{quantity > 1 && "s"} x{" "} {numberToCurrency(cp.product.price)} From 82e4033cdfd7ec6a3067b35171e042523f7dfd4b Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Mon, 2 Dec 2024 00:40:19 +0700 Subject: [PATCH 11/13] feat: add filter by purchase date --- resources/js/Components/DatepickerFilter.tsx | 59 +++++++ .../js/Components/Forms/RangeDatepicker.tsx | 22 +-- .../Product => }/Components/PopupFilter.tsx | 0 resources/js/Pages/Product/Index.tsx | 2 +- resources/js/Pages/Transaction/Index.tsx | 145 ++++++++++-------- .../Pages/Transaction/container/NotFound.tsx | 2 +- 6 files changed, 159 insertions(+), 71 deletions(-) create mode 100644 resources/js/Components/DatepickerFilter.tsx rename resources/js/{Pages/Product => }/Components/PopupFilter.tsx (100%) diff --git a/resources/js/Components/DatepickerFilter.tsx b/resources/js/Components/DatepickerFilter.tsx new file mode 100644 index 0000000..f3f1198 --- /dev/null +++ b/resources/js/Components/DatepickerFilter.tsx @@ -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>; + onResetFilter?: () => void; +} & React.ComponentPropsWithoutRef<"div">; + +type FormData = { + filter: Date[]; +}; + +export default function DatepickerFilter({ + date, + setDate, + onResetFilter, +}: PopupFilterProps) { + //#region //*=========== Form ===========t + const methods = useForm({ + 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 ( + +
+ 0} + onClearDate={resetFilter} + /> +
+
+ ); +} diff --git a/resources/js/Components/Forms/RangeDatepicker.tsx b/resources/js/Components/Forms/RangeDatepicker.tsx index b4b06be..1bf1df6 100644 --- a/resources/js/Components/Forms/RangeDatepicker.tsx +++ b/resources/js/Components/Forms/RangeDatepicker.tsx @@ -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"; @@ -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; export default function RangeDatePicker({ @@ -31,11 +31,12 @@ export default function RangeDatePicker({ placeholder, defaultYear, defaultMonth, - defaultValue, helperText, readOnly = false, hideError = false, disabled, + isClearable, + onClearDate, containerClassName, }: ReactDatePickerProps) { const { @@ -76,7 +77,6 @@ export default function RangeDatePicker({ { const startDate = Array.isArray(value) @@ -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", @@ -118,10 +118,14 @@ export default function RangeDatePicker({ disabled={disabled} selectsRange /> - +
+ {isClearable && ( + + )} + +
{helperText && ( diff --git a/resources/js/Pages/Product/Components/PopupFilter.tsx b/resources/js/Components/PopupFilter.tsx similarity index 100% rename from resources/js/Pages/Product/Components/PopupFilter.tsx rename to resources/js/Components/PopupFilter.tsx diff --git a/resources/js/Pages/Product/Index.tsx b/resources/js/Pages/Product/Index.tsx index 452f218..b29f3c5 100644 --- a/resources/js/Pages/Product/Index.tsx +++ b/resources/js/Pages/Product/Index.tsx @@ -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[]; diff --git a/resources/js/Pages/Transaction/Index.tsx b/resources/js/Pages/Transaction/Index.tsx index 1f61a86..8db9a4b 100644 --- a/resources/js/Pages/Transaction/Index.tsx +++ b/resources/js/Pages/Transaction/Index.tsx @@ -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"; @@ -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 = { @@ -29,20 +28,36 @@ type TransactionIndexProps = { transaction: TransactionType[]; category: CategoryType[]; categoryIds: string[]; + startDate: string; + endDate: string; +}; + +type QueryType = { + category_id: string[]; + start_date?: string; + end_date?: string; }; export default function TransactionIndex({ transaction, category, categoryIds, + startDate, + endDate, }: TransactionIndexProps) { const [filter, setFilter] = React.useState(""); - + const [filterDate, setFilterDate] = React.useState( + [ + startDate ? new Date(startDate) : null, + endDate ? new Date(endDate) : null, + ].filter(Boolean) as Date[], + ); const [filterQuery, setFilterQuery] = React.useState({ category: categoryIds, }); const prevCategory = React.useRef(categoryIds); + const prevDate = React.useRef(filterDate); const filterOption: PopupFilterProps["filterOption"] = React.useMemo( @@ -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_id: 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 ( - {transaction.length === 0 ? ( - - ) : ( -
- - Order List - - -
-
-
- -
- - {filter !== "" && ( -
- -
- )} -
-
- setFilterQuery({ category: [] })} - /> +
+ + Order List + +
+
+
+
+ + {filter !== "" && ( +
+ +
+ )} +
+
+ setFilterDate([])} + /> + setFilterQuery({ category: [] })} + />
+
+ {filteredTransaction.length === 0 ? ( + + ) : (
{filteredTransaction.map((t) => (
))}
-
- )} + )} +
); } diff --git a/resources/js/Pages/Transaction/container/NotFound.tsx b/resources/js/Pages/Transaction/container/NotFound.tsx index f29c5c1..5ce2252 100644 --- a/resources/js/Pages/Transaction/container/NotFound.tsx +++ b/resources/js/Pages/Transaction/container/NotFound.tsx @@ -3,7 +3,7 @@ import Typography from "@/Components/Typography"; export default function NotFound() { return ( -
+
Date: Mon, 2 Dec 2024 01:23:20 +0700 Subject: [PATCH 12/13] fix: cart cant checkout one item --- resources/js/Pages/Cart/Index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/js/Pages/Cart/Index.tsx b/resources/js/Pages/Cart/Index.tsx index 7b3a207..c7e39ce 100644 --- a/resources/js/Pages/Cart/Index.tsx +++ b/resources/js/Pages/Cart/Index.tsx @@ -27,7 +27,13 @@ const CartIndex = ({ cart }: { cart: CartType[] }) => { const readyToCheckout = cart.filter((item) => checkedOut.includes(item.id.toString())) ?? []; - transform((data) => ({ ...data, ...getValues() })); + transform((data) => ({ + ...data, + product_id: + typeof getValues("product_id") === "string" + ? [getValues("product_id")] + : getValues("product_id"), + })); const onSubmit: SubmitHandler = async () => { post(route("cart.checkout"), { From a61cd57cb3d93d01f0e351f34bfeb8ca664f1152 Mon Sep 17 00:00:00 2001 From: nisrinasalm Date: Mon, 2 Dec 2024 16:45:38 +0700 Subject: [PATCH 13/13] fix: fix timezone --- app/Http/Controllers/CartController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/CartController.php b/app/Http/Controllers/CartController.php index 3409c51..dde95e6 100644 --- a/app/Http/Controllers/CartController.php +++ b/app/Http/Controllers/CartController.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Inertia\Inertia; +use Illuminate\Support\Carbon; class CartController extends Controller { @@ -94,6 +95,7 @@ function checkout(Request $request) } $transaction->total_price = $total_price; + $transaction->created_at = Carbon::now('Asia/Jakarta'); $transaction->save(); for($i = 0; $i < count($validatedCart['product_id']); $i++) {