diff --git a/README.md b/README.md new file mode 100644 index 0000000..df7576d --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +## AwaiShop + +A Simple E-Commerce System with C# & SignalR & FluentUI3 & AzureAD + +## Basic Requirements + +- .NET 8 +- Node.js 17+ +- Visual Studio 2022 Preview +- Visual Studio Code diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs b/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs index 5084180..5595f0d 100644 --- a/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs +++ b/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs @@ -106,13 +106,13 @@ private async Task deleteType(Type type) { * * @author Aloento * @since 0.1.0 - * @version 0.2.0 + * @version 0.3.0 * */ - public async Task ProductDeleteType(uint variantId, string reqType) { + public async Task ProductDeleteType(uint typeId) { await this.deleteType( await this.Db.Types - .Where(x => x.VariantId == variantId && x.Name == reqType) + .Where(x => x.TypeId == typeId) .IncludeOptimized(x => x.Combos) .SingleAsync() ); diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Get.cs b/SoarCraft.AwaiShop/AdminHub/Product/Get.cs index 9dc6c86..b0f3d95 100644 --- a/SoarCraft.AwaiShop/AdminHub/Product/Get.cs +++ b/SoarCraft.AwaiShop/AdminHub/Product/Get.cs @@ -36,15 +36,25 @@ await this.Db.Products * * @author Aloento * @since 0.1.0 - * @version 1.0.0 + * @version 1.1.0 * */ - public async Task ProductGetVariants(uint prodId) => - await this.Db.Variants + public Task ProductGetVariants(uint prodId) => + this.Db.Variants .Where(x => x.ProductId == prodId) - .Select(x => new { - x.VariantId, - Types = x.Types.Select(t => t.TypeId).ToArray() - }) + .Select(x => x.VariantId) + .ToArrayAsync(); + + /** + * + * @author Aloento + * @since 1.3.0 + * @version 0.1.0 + * + */ + public Task ProductGetTypes(uint variantId) => + this.Db.Types + .Where(x => x.VariantId == variantId) + .Select(x => x.TypeId) .ToArrayAsync(); } diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs b/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs index d7da6db..414075b 100644 --- a/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs +++ b/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs @@ -228,10 +228,10 @@ private async Task> archiveCombos(ICollection oldCombos) { * * @author Aloento * @since 0.1.0 - * @version 1.0.0 + * @version 1.1.0 * */ - public async Task ProductPatchType(uint variantId, string oldName, string newName) { + public async Task ProductPatchType(uint typeId, string newName) { var valid = typeof(Type) .GetProperty(nameof(Type.Name))! .GetCustomAttribute()!; @@ -240,7 +240,7 @@ public async Task ProductPatchType(uint variantId, string oldName, string throw new HubException(valid.FormatErrorMessage("Name")); var type = this.Db.Types - .Where(x => x.VariantId == variantId && x.Name == oldName); + .Where(x => x.TypeId == typeId); var any = await type .SelectMany(x => x.Combos) diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Post.cs b/SoarCraft.AwaiShop/AdminHub/Product/Post.cs index 728c02d..d35cb28 100644 --- a/SoarCraft.AwaiShop/AdminHub/Product/Post.cs +++ b/SoarCraft.AwaiShop/AdminHub/Product/Post.cs @@ -110,7 +110,7 @@ public async Task ProductPostPhoto(uint prodId, IAsyncEnumerable i * * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.1.1 * */ public async Task ProductPostVariant(uint prodId, string name) { @@ -138,14 +138,14 @@ public async Task ProductPostVariant(uint prodId, string name) { }); await this.Db.SaveChangesAsync(); - return temp.Entity.ProductId; + return temp.Entity.VariantId; } /** * * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.1.1 * */ public async Task ProductPostType(uint variantId, string name) { @@ -173,7 +173,7 @@ public async Task ProductPostType(uint variantId, string name) { }); await this.Db.SaveChangesAsync(); - return temp.Entity.VariantId; + return temp.Entity.TypeId; } /** diff --git a/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs b/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs index 2f810c0..4ca8e9b 100644 --- a/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs +++ b/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs @@ -6,13 +6,23 @@ namespace SoarCraft.AwaiShop.Helpers; * * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.2.0 * */ internal static partial class LoggerExtension { [LoggerMessage( EventId = 1001, Level = LogLevel.Debug, + Message = "Guest : Visit from [{ip}]" + )] + private static partial void guestVisit(ILogger logger, string? ip); + + public static void GuestVisit(this ILogger logger, HubCallerContext ctx) => + guestVisit(logger, ctx.GetHttpContext()?.Connection.RemoteIpAddress?.ToString()); + + [LoggerMessage( + EventId = 2001, + Level = LogLevel.Information, Message = "User {name} : [{uid}] Logged from [{ip}]" )] private static partial void userLogin(ILogger logger, string? name, string? uid, string? ip); diff --git a/SoarCraft.AwaiShop/Hub/ShopHub.cs b/SoarCraft.AwaiShop/Hub/ShopHub.cs index a606538..b2e1bd2 100644 --- a/SoarCraft.AwaiShop/Hub/ShopHub.cs +++ b/SoarCraft.AwaiShop/Hub/ShopHub.cs @@ -18,7 +18,7 @@ internal partial class ShopHub(ShopContext db, ILogger logger) : CraftH * * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.1.1 * */ public override async Task OnConnectedAsync() { @@ -34,7 +34,8 @@ public override async Task OnConnectedAsync() { await this.Clients.Caller.OnNewUser(); this.Context.Items.TryAdd("NewUser", true); } - } + } else + this.Logger.GuestVisit(this.Context); } /** diff --git a/package.json b/package.json index 9020100..706d1f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "awaishop", "private": true, - "version": "1.4.0", + "version": "1.4.5", "type": "module", "author": { "name": "Aloento", diff --git a/src/Pages/Admin/Order/Ship.tsx b/src/Pages/Admin/Order/Ship.tsx index 9f73f30..98325da 100644 --- a/src/Pages/Admin/Order/Ship.tsx +++ b/src/Pages/Admin/Order/Ship.tsx @@ -1,7 +1,7 @@ import { Button, Field, Input, Toast, ToastTitle } from "@fluentui/react-components"; import { EditRegular, SendRegular } from "@fluentui/react-icons"; import { useBoolean } from "ahooks"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useOrder } from "~/Components/Order/useOrder"; import { Logger } from "~/Helpers/Logger"; import { useErrorToast } from "~/Helpers/useToast"; @@ -12,14 +12,18 @@ const log = new Logger("Admin", "Order", "Detail", "Shipment"); /** * @author Aloento * @since 0.5.0 - * @version 0.3.0 + * @version 0.3.1 */ export function Shipment({ OrderId }: { OrderId: number }) { const [edit, { setTrue, setFalse }] = useBoolean(); const { dispatch, dispatchToast } = useErrorToast(log); const { data: order, mutate } = useOrder(OrderId, true); - const [track, setTrack] = useState(order?.TrackingNumber); + const [track, setTrack] = useState(""); + + useEffect(() => { + order?.TrackingNumber && setTrack(order?.TrackingNumber); + }, [order]); const { run } = AdminHub.Order.Post.useShip({ manual: true, diff --git a/src/Pages/Admin/Product/Combo/Detail.tsx b/src/Pages/Admin/Product/Combo/Detail.tsx index e948c2c..17c00c1 100644 --- a/src/Pages/Admin/Product/Combo/Detail.tsx +++ b/src/Pages/Admin/Product/Combo/Detail.tsx @@ -1,23 +1,23 @@ import { Button, Combobox, DataGridCell, DataGridHeaderCell, Dialog, DialogBody, DialogContent, DialogSurface, DialogTitle, DialogTrigger, Label, Option, SpinButton, TableColumnDefinition, Toast, ToastTitle, createTableColumn, makeStyles, tokens } from "@fluentui/react-components"; import { DismissRegular, EditRegular } from "@fluentui/react-icons"; -import { useBoolean, useRequest } from "ahooks"; +import { useAsyncEffect, useBoolean } from "ahooks"; import { useState } from "react"; import { DelegateDataGrid } from "~/Components/DataGrid"; import { Logger } from "~/Helpers/Logger"; import { Flex } from "~/Helpers/Styles"; import { useErrorToast } from "~/Helpers/useToast"; +import { Hub } from "~/ShopNet"; import { AdminHub } from "~/ShopNet/Admin"; import { IComboItem } from "."; -import { IVariantItem } from "../Variant"; +import { IUpdateComboItem, IVariantItem } from "./New"; /** * @author Aloento * @since 0.5.0 * @version 0.1.0 */ -interface IEditComboItem extends IVariantItem { +interface IEditComboItem extends IUpdateComboItem { Current: string; - Update: (type: string) => void; } /** @@ -80,6 +80,7 @@ const useStyles = makeStyles({ */ export interface IDetailComboItem extends IComboItem { ProdId: number; + /** @deprecated */ Refresh: () => void; } @@ -88,21 +89,48 @@ const log = new Logger("Admin", "Product", "Detail", "Combo", "Detail"); /** * @author Aloento * @since 0.5.0 - * @version 0.2.3 + * @version 0.3.0 */ export function AdminProductComboDetail({ Id, ProdId, Combo, Stock, Refresh }: IDetailComboItem) { const [open, { toggle }] = useBoolean(); const [combo, setCombo] = useState(Combo); const [stock, setStock] = useState(Stock); - const { data: varis } = useRequest(() => AdminHub.Product.Get.Variants(ProdId, log), { + const [varis, setVaris] = useState([]); + const { data: varIds } = AdminHub.Product.Get.useVariants(ProdId, { onError: log.error }); + useAsyncEffect(async () => { + if (!varIds) + return; + + const varis: IVariantItem[] = []; + + for (const i of varIds) { + const typeIds = await AdminHub.Product.Get.Types(i); + const types = []; + + for (const typeId of typeIds) { + const type = await Hub.Product.Get.Type(typeId); + types.push(type); + } + + const { Name } = await Hub.Product.Get.Variant(i); + + varis.push({ + Id: i, + Name: Name, + Types: types.map(x => x.Name) + }); + } + + setVaris(varis); + }, [varIds]); + const { dispatch, dispatchToast } = useErrorToast(log); - const { run } = AdminHub.Product.Patch.useCombo({ - manual: true, + const { run, loading } = AdminHub.Product.Patch.useCombo({ onError(e, req) { dispatch({ Message: "Failed Update Combo", @@ -167,7 +195,13 @@ export function AdminProductComboDetail({ Id, ProdId, Combo, Stock, Refresh }: I setStock(val); }} /> - + diff --git a/src/Pages/Admin/Product/Combo/New.tsx b/src/Pages/Admin/Product/Combo/New.tsx index 4cf24f9..0e0fc88 100644 --- a/src/Pages/Admin/Product/Combo/New.tsx +++ b/src/Pages/Admin/Product/Combo/New.tsx @@ -1,20 +1,31 @@ import { Button, Combobox, DataGridCell, DataGridHeaderCell, Dialog, DialogBody, DialogContent, DialogSurface, DialogTitle, DialogTrigger, Label, Option, SpinButton, TableColumnDefinition, Toast, ToastTitle, createTableColumn, makeStyles, tokens } from "@fluentui/react-components"; import { AddRegular, DismissRegular } from "@fluentui/react-icons"; -import { useBoolean, useRequest } from "ahooks"; +import { useAsyncEffect, useBoolean } from "ahooks"; import { useState } from "react"; import { DelegateDataGrid } from "~/Components/DataGrid"; import { Logger } from "~/Helpers/Logger"; import { Flex } from "~/Helpers/Styles"; import { useErrorToast } from "~/Helpers/useToast"; +import { Hub } from "~/ShopNet"; import { AdminHub } from "~/ShopNet/Admin"; -import { IVariantItem } from "../Variant"; /** * @author Aloento * @since 0.5.0 * @version 0.1.0 */ -interface INewComboItem extends IVariantItem { +export interface IVariantItem { + Id: number; + Name: string; + Types: string[]; +} + +/** + * @author Aloento + * @since 0.5.0 + * @version 0.1.0 + */ +export interface IUpdateComboItem extends IVariantItem { Update: (type: string) => void; } @@ -23,8 +34,8 @@ interface INewComboItem extends IVariantItem { * @since 0.5.0 * @version 0.1.0 */ -const columns: TableColumnDefinition[] = [ - createTableColumn({ +const columns: TableColumnDefinition[] = [ + createTableColumn({ columnId: "Variant", renderHeaderCell: () => { return Variant @@ -33,7 +44,7 @@ const columns: TableColumnDefinition[] = [ return {item.Name} } }), - createTableColumn({ + createTableColumn({ columnId: "Type", renderHeaderCell: () => { return Type @@ -72,26 +83,52 @@ const log = new Logger("Admin", "Product", "Detail", "Combo", "NewCombo"); /** * @author Aloento * @since 0.5.0 - * @version 0.2.3 + * @version 1.0.0 */ export function AdminProductNewCombo({ ProdId, Refresh }: { ProdId: number; Refresh: () => void }) { const [open, { toggle }] = useBoolean(); + + const [varis, setVaris] = useState([]); const [combo, setCombo] = useState>({}); const [stock, setStock] = useState(1); - const { data: varis } = useRequest(() => AdminHub.Product.Get.Variants(ProdId, log), { - onSuccess(data) { - for (const i of data) - combo[i.Name] = ""; - - setCombo({ ...combo }); - }, + const { data: varIds } = AdminHub.Product.Get.useVariants(ProdId, { onError: log.error }); + useAsyncEffect(async () => { + if (!varIds) + return; + + const varis: IVariantItem[] = []; + + for (const i of varIds) { + const typeIds = await AdminHub.Product.Get.Types(i); + const types = []; + + for (const typeId of typeIds) { + const type = await Hub.Product.Get.Type(typeId); + types.push(type); + } + + const { Name } = await Hub.Product.Get.Variant(i); + + varis.push({ + Id: i, + Name: Name, + Types: types.map(x => x.Name) + }); + + combo[Name] = ""; + } + + setVaris(varis); + setCombo({ ...combo }); + }, [varIds]); + const { dispatch, dispatchToast } = useErrorToast(log); - const { run } = AdminHub.Product.Post.useCombo({ + const { run, loading } = AdminHub.Product.Post.useCombo({ onError(e, req) { dispatch({ Message: "Failed Create Combo", @@ -154,7 +191,13 @@ export function AdminProductNewCombo({ ProdId, Refresh }: { ProdId: number; Refr setStock(val); }} /> - + diff --git a/src/Pages/Admin/Product/Variant/Delete.tsx b/src/Pages/Admin/Product/Variant/Delete.tsx index d9ba861..57c301b 100644 --- a/src/Pages/Admin/Product/Variant/Delete.tsx +++ b/src/Pages/Admin/Product/Variant/Delete.tsx @@ -9,12 +9,13 @@ const log = new Logger("Admin", "Product", "Detail", "Variant", "Delete"); /** * @author Aloento * @since 0.5.0 - * @version 0.1.2 + * @version 0.2.0 + * @todo Add the ability to refresh the variant list */ -export function AdminProductVariantDelete({ VariantId, Refresh }: { VariantId: number; Refresh: () => void }) { +export function AdminProductVariantDelete({ VariantId }: { VariantId: number; }) { const { dispatch, dispatchToast } = useErrorToast(log); - const { run } = AdminHub.Product.Delete.useVariant({ + const { run, loading } = AdminHub.Product.Delete.useVariant(VariantId, { onError(e, req) { dispatch({ Message: "Failed Delete Variant", @@ -29,16 +30,15 @@ export function AdminProductVariantDelete({ VariantId, Refresh }: { VariantId: n , { intent: "success" } ); - - Refresh(); } }); return ( diff --git a/src/Pages/Admin/Product/Variant/Edit/index.tsx b/src/Pages/Admin/Product/Variant/Edit/index.tsx index 10352d2..b6c5919 100644 --- a/src/Pages/Admin/Product/Variant/Edit/index.tsx +++ b/src/Pages/Admin/Product/Variant/Edit/index.tsx @@ -2,21 +2,15 @@ import { Button, DataGridCell, DataGridHeaderCell, Dialog, DialogActions, Dialog import { DismissRegular, EditRegular } from "@fluentui/react-icons"; import { DelegateDataGrid } from "~/Components/DataGrid"; import { ColFlex } from "~/Helpers/Styles"; -import { IVariantItem } from ".."; +import { Hub } from "~/ShopNet"; +import { AdminHub } from "~/ShopNet/Admin"; import { AdminProductTypeDelete } from "./Delete"; import { AdminProductVariantName } from "./Name"; import { AdminProductType } from "./Type"; -/** - * @author Aloento - * @since 0.5.0 - * @version 0.1.0 - */ interface ITypeItem { Id: number; - Name: string; VariantId: number; - Refresh: () => void; } /** @@ -38,19 +32,20 @@ const useStyles = makeStyles({ /** * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.2.0 */ const columns: TableColumnDefinition[] = [ - createTableColumn({ + createTableColumn({ columnId: "Name", renderHeaderCell: () => { return Name }, - renderCell(item) { - return {item.Name} + renderCell({ Id }) { + const { data } = Hub.Product.Get.useType(Id) + return {data?.Name} } }), - createTableColumn({ + createTableColumn({ columnId: "Action", renderHeaderCell: () => { return ( @@ -59,12 +54,12 @@ const columns: TableColumnDefinition[] = [ ) }, - renderCell(item) { + renderCell({ Id, VariantId }) { return ( - + - + ) } @@ -74,9 +69,11 @@ const columns: TableColumnDefinition[] = [ /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ -export function AdminProductVariantEdit({ Variant, Refresh }: { Variant: IVariantItem; Refresh: () => void }) { +export function AdminProductVariantEdit({ VariantId }: { VariantId: number; }) { + const { data } = AdminHub.Product.Get.useTypes(VariantId); + return ( @@ -97,16 +94,16 @@ export function AdminProductVariantEdit({ Variant, Refresh }: { Variant: IVarian - + ((v, i) => ({ Id: i, Name: v, VariantId: Variant.Id, Refresh }))} + Items={data?.map(x => ({ Id: x, VariantId }))} Columns={columns} /> - + diff --git a/src/Pages/Admin/Product/Variant/New.tsx b/src/Pages/Admin/Product/Variant/New.tsx index fac77c1..4fa66fe 100644 --- a/src/Pages/Admin/Product/Variant/New.tsx +++ b/src/Pages/Admin/Product/Variant/New.tsx @@ -24,16 +24,16 @@ const log = new Logger("Admin", "Product", "Detail", "Variant", "New"); /** * @author Aloento * @since 0.5.0 - * @version 0.1.2 + * @version 0.2.0 */ -export function AdminProductNewVariant({ ProdId, Refresh }: { ProdId: number; Refresh: () => void }) { +export function AdminProductNewVariant({ ProdId }: { ProdId: number; }) { const style = useStyles(); const [open, { toggle }] = useBoolean(); const [name, setName] = useState(""); const { dispatch, dispatchToast } = useErrorToast(log); - const { run } = AdminHub.Product.Post.useVariant({ + const { run, loading } = AdminHub.Product.Post.useVariant(ProdId, { onError(e, params) { dispatch({ Message: "Failed Create Variant", @@ -49,7 +49,6 @@ export function AdminProductNewVariant({ ProdId, Refresh }: { ProdId: number; Re { intent: "success" } ); - Refresh(); setName(""); toggle(); } @@ -68,7 +67,10 @@ export function AdminProductNewVariant({ ProdId, Refresh }: { ProdId: number; Re setName(e.value)} /> - diff --git a/src/Pages/Admin/Product/Variant/index.tsx b/src/Pages/Admin/Product/Variant/index.tsx index 37edcaf..bbea501 100644 --- a/src/Pages/Admin/Product/Variant/index.tsx +++ b/src/Pages/Admin/Product/Variant/index.tsx @@ -1,24 +1,13 @@ import { DataGridCell, DataGridHeaderCell, Subtitle1, TableColumnDefinition, createTableColumn, makeStyles } from "@fluentui/react-components"; -import { useRequest } from "ahooks"; import { DelegateDataGrid } from "~/Components/DataGrid"; import { Logger } from "~/Helpers/Logger"; import { Flex } from "~/Helpers/Styles"; +import { Hub } from "~/ShopNet"; import { AdminHub } from "~/ShopNet/Admin"; import { AdminProductVariantDelete } from "./Delete"; import { AdminProductVariantEdit } from "./Edit"; import { AdminProductNewVariant } from "./New"; -/** - * @author Aloento - * @since 0.5.0 - * @version 0.1.0 - */ -export interface IVariantItem { - Id: number; - Name: string; - Types: string[]; -} - /** * @author Aloento * @since 0.5.0 @@ -48,10 +37,10 @@ const log = new Logger("Admin", "Product", "Detail", "Variant"); /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ -const columns: TableColumnDefinition[] = [ - createTableColumn({ +const columns: TableColumnDefinition[] = [ + createTableColumn({ columnId: "Id", renderHeaderCell: () => { return ( @@ -63,12 +52,12 @@ const columns: TableColumnDefinition[] = [ renderCell(item) { return ( - {item.Id} + {item} ) } }), - createTableColumn({ + createTableColumn({ columnId: "Name", renderHeaderCell: () => { return ( @@ -78,31 +67,39 @@ const columns: TableColumnDefinition[] = [ ) }, renderCell(item) { + const { data } = Hub.Product.Get.useVariant(item, { + onError: log.error + }); + return ( - {item.Name} + {data?.Name} ) } }), - createTableColumn({ + createTableColumn({ columnId: "Type", renderHeaderCell: () => { return Type }, renderCell(item) { + const { data } = AdminHub.Product.Get.useTypeList(item, { + onError: log.error + }); + return ( { - item.Types.reduce((prev, curr) => { - return `${prev} ${curr} ;` + data?.reduce((prev, { Name }) => { + return `${prev} ${Name} ;` }, "") } ) } }), - createTableColumn({ + createTableColumn({ columnId: "Action", renderHeaderCell: () => { return ( @@ -114,9 +111,9 @@ const columns: TableColumnDefinition[] = [ renderCell(item) { return ( - + - + ) } @@ -126,29 +123,21 @@ const columns: TableColumnDefinition[] = [ /** * @author Aloento * @since 0.5.0 - * @version 0.1.0 - */ -let refreshVariant: () => void; - -/** - * @author Aloento - * @since 0.5.0 - * @version 0.2.1 + * @version 0.3.0 */ export function AdminProductVariant({ ProdId }: { ProdId: number }) { const style = useStyles(); - const { data, run } = useRequest(() => AdminHub.Product.Get.Variants(ProdId, log), { + const { data } = AdminHub.Product.Get.useVariants(ProdId, { onError: log.error }); - refreshVariant = run; return <>
Variant - +
- + x} /> } diff --git a/src/ShopNet/Admin/Product/Delete.ts b/src/ShopNet/Admin/Product/Delete.ts index fd525cf..833878e 100644 --- a/src/ShopNet/Admin/Product/Delete.ts +++ b/src/ShopNet/Admin/Product/Delete.ts @@ -35,10 +35,10 @@ export abstract class AdminProductDelete extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ - public static useVariant(options: Options) { - return useRequest(async (variantId) => { + public static useVariant(variantId: number, options: Options) { + return useRequest(async () => { const res = await this.Invoke("ProductDeleteVariant", variantId); this.EnsureTrue(res); return res; @@ -51,11 +51,11 @@ export abstract class AdminProductDelete extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ - public static useType(options: Options) { - return useRequest(async (variantId, type) => { - const res = await this.Invoke("ProductDeleteType", variantId, type); + public static useType(typeId: number, options: Options) { + return useRequest(async () => { + const res = await this.Invoke("ProductDeleteType", typeId); this.EnsureTrue(res); return res; }, { diff --git a/src/ShopNet/Admin/Product/Get.ts b/src/ShopNet/Admin/Product/Get.ts index a3406b0..5b58601 100644 --- a/src/ShopNet/Admin/Product/Get.ts +++ b/src/ShopNet/Admin/Product/Get.ts @@ -1,9 +1,12 @@ import { useConst } from "@fluentui/react-hooks"; +import { Options } from "ahooks/lib/useRequest/src/types"; import dayjs from "dayjs"; import { useLiveQuery } from "dexie-react-hooks"; +import { useEffect } from "react"; import type { Logger } from "~/Helpers/Logger"; +import { useSWR } from "~/Helpers/useSWR"; import { IProductCount } from "~/Pages/Admin/Product"; -import { IVariantItem } from "~/Pages/Admin/Product/Variant"; +import { ProductData } from "~/ShopNet/Product/Data"; import { ProductGet } from "~/ShopNet/Product/Get"; import { AdminNet } from "../AdminNet"; @@ -17,7 +20,6 @@ export abstract class AdminProductGet extends AdminNet { protected static override readonly Log = [...super.Log, "Product", "Get"]; public static readonly list = "ProductGetList"; - /** * @author Aloento * @since 0.5.0 @@ -27,7 +29,7 @@ export abstract class AdminProductGet extends AdminNet { const log = useConst(() => pLog.With(...this.Log, "List")); const res = useLiveQuery(() => - this.GetTimeCache("", this.list, (x) => x.add(1, "m")) + this.GetTimeCache("", this.list, (x) => x.add(5, "s")) .catch(log.error) ); @@ -35,7 +37,7 @@ export abstract class AdminProductGet extends AdminNet { } /** @deprecated */ public static ListUpdate(action: (raw: number[]) => number[]) { - return this.UpdateCache(action, "", this.list, dayjs().add(1, "m")); + return this.UpdateCache(action, "", this.list, dayjs().add(5, "s")); } /** @@ -44,7 +46,7 @@ export abstract class AdminProductGet extends AdminNet { * @version 0.1.0 */ public static Count(prodId: number): Promise { - return this.GetTimeCache(prodId, "ProductGetCount", (x) => x.add(1, "m"), prodId); + return this.GetTimeCache(prodId, "ProductGetCount", (x) => x.add(5, "s"), prodId); } /** @@ -75,51 +77,106 @@ export abstract class AdminProductGet extends AdminNet { return prod.Category; } + public static readonly variants = "ProductGetVariants"; /** * @author Aloento * @since 0.5.0 - * @version 1.0.1 + * @version 2.0.0 */ - public static async Variants(prodId: number, pLog: Logger): Promise { - const log = pLog.With(...this.Log, "Variants"); + public static useVariants(prodId: number, options?: Options) { + const index = useConst(() => this.Index(prodId, this.variants)); + + const req = useSWR( + index, + async (id) => { + await this.getLocker(index); + this.reqPool.add(index); - const list = await this.GetTimeCache< + const list = await this.Invoke(this.variants, id) + .finally(() => this.reqPool.delete(index)); + + return list; + }, { - VariantId: number; - Types: number[]; - }[] - >(prodId, "ProductGetVariants", (x) => x.add(1, "m"), prodId); + ...options, + defaultParams: [prodId], + } + ); - const items: IVariantItem[] = []; + return req; + } - for (const meta of list) { - const vari = await ProductGet.Variant(meta.VariantId); + public static readonly types = "ProductGetTypes"; + /** + * @author Aloento + * @since 1.4.5 + * @version 0.1.0 + */ + public static async Types(variantId: number) { + return this.GetTimeCache(variantId, this.types, (x) => x.add(5, "s"), variantId); + } - if (!vari) { - log.warn(`Variant ${meta} Not Found. Product : ${prodId}`); - continue; + /** + * @author Aloento + * @since 0.5.0 + * @version 2.0.0 + */ + public static useTypes( + variantId: number, + options?: Options + ) { + const index = useConst(() => this.Index(variantId, this.types)); + + const req = useSWR( + index, + (variantId) => this.Invoke(this.types, variantId), + { + ...options, + defaultParams: [variantId] } + ); - const types: string[] = []; - - for (const typeId of meta.Types) { - const type = await ProductGet.Type(typeId); + return req; + } - if (!type) { - log.warn(`Type ${typeId} Not Found. Variant : ${meta.VariantId}, Product : ${prodId}`); - continue; + /** + * @author Aloento + * @since 1.4.0 + * @version 0.1.0 + */ + public static useTypeList( + variantId: number, + options?: Options + ) { + const { data } = this.useTypes(variantId); + const index = useConst(() => this.Index(variantId, "TypeList")); + + const req = useSWR( + index, + async () => { + if (!data) + return []; + + const types: ProductData.Type[] = []; + + for (const typeId of data) { + const type = await ProductData.Type(typeId); + types.push(type); } - types.push(type.Name); + return types; + }, + { + ...options, + useMemory: true } + ); - items.push({ - Id: meta.VariantId, - Name: vari.Name, - Types: types - }); - } + useEffect(() => { + if (data) + req.refresh(); + }, [data]); - return items; + return req; } } diff --git a/src/ShopNet/Admin/Product/Patch.ts b/src/ShopNet/Admin/Product/Patch.ts index 4ad21f0..3b3289b 100644 --- a/src/ShopNet/Admin/Product/Patch.ts +++ b/src/ShopNet/Admin/Product/Patch.ts @@ -114,12 +114,20 @@ export abstract class AdminProductPatch extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ - public static useVariantName(options: Options) { - return useRequest(async (variantId, name) => { + public static useVariantName(variantId: number, options: Options) { + const { mutate } = ProductData.useVariant(variantId); + + return useRequest(async (name) => { const res = await this.Invoke("ProductPatchVariantName", variantId, name); this.EnsureTrue(res); + + mutate((raw) => { + raw!.Name = name; + return raw; + }); + return res; }, { ...options, @@ -130,12 +138,20 @@ export abstract class AdminProductPatch extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ - public static useType(options: Options) { - return useRequest(async (variantId, oldName, newName) => { - const res = await this.Invoke("ProductPatchType", variantId, oldName, newName); + public static useType(typeId: number, options: Options) { + const { mutate } = ProductData.useType(typeId); + + return useRequest(async (newName) => { + const res = await this.Invoke("ProductPatchType", typeId, newName); this.EnsureTrue(res); + + mutate((raw) => { + raw!.Name = newName; + return raw; + }); + return res; }, { ...options, diff --git a/src/ShopNet/Admin/Product/Post.ts b/src/ShopNet/Admin/Product/Post.ts index 6df3d2f..37329cc 100644 --- a/src/ShopNet/Admin/Product/Post.ts +++ b/src/ShopNet/Admin/Product/Post.ts @@ -101,11 +101,17 @@ export abstract class AdminProductPost extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ - public static useVariant(options: Options) { + public static useVariant(prodId: number, options: Options) { + const { mutate } = AdminProductGet.useVariants(prodId); + return useRequest( - (prodId, name) => this.Invoke("ProductPostVariant", prodId, name), + async (name) => { + const res = await this.Invoke("ProductPostVariant", prodId, name); + mutate(x => [res, ...x || []]); + return res; + }, { ...options, manual: true @@ -115,11 +121,17 @@ export abstract class AdminProductPost extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ - public static useType(options: Options) { + public static useType(variantId: number, options: Options) { + const { mutate } = AdminProductGet.useTypes(variantId); + return useRequest( - (variantId, name) => this.Invoke("ProductPostType", variantId, name), + async (name) => { + const res = await this.Invoke("ProductPostType", variantId, name); + mutate(x => [res, ...x || []]); + return res; + }, { ...options, manual: true diff --git a/src/ShopNet/Product/Data.ts b/src/ShopNet/Product/Data.ts index 3311347..0e20614 100644 --- a/src/ShopNet/Product/Data.ts +++ b/src/ShopNet/Product/Data.ts @@ -94,12 +94,14 @@ export abstract class ProductData extends ShopNet { { ...options, defaultParams: [key], + useMemory: true } ); return req; } + public static readonly type = "TypeEntity"; /** * @author Aloento * @since 1.0.0 @@ -110,6 +112,28 @@ export abstract class ProductData extends ShopNet { return this.GetVersionCache(key, "TypeEntity"); } + /** + * @author Aloento + * @since 1.4.5 + * @version 0.1.0 + */ + public static useType(key: number, options?: Options) { + const index = useConst(() => this.Index(key, this.type)); + + const req = useSWR( + index, + (id) => this.Type(id), + { + ...options, + defaultParams: [key], + useMemory: true + } + ); + + return req; + } + + public static readonly variant = "VariantEntity"; /** * @author Aloento * @since 1.0.0 @@ -120,6 +144,27 @@ export abstract class ProductData extends ShopNet { return this.GetVersionCache(key, "VariantEntity"); } + /** + * @author Aloento + * @since 1.4.5 + * @version 0.1.0 + */ + public static useVariant(key: number, options?: Options) { + const index = useConst(() => this.Index(key, this.variant)); + + const req = useSWR( + index, + (id) => this.Variant(id), + { + ...options, + defaultParams: [key], + useMemory: true + } + ); + + return req; + } + /** * @author Aloento * @since 1.3.5 diff --git a/src/ShopNet/Product/Get.ts b/src/ShopNet/Product/Get.ts index 71f2f3b..6c3b539 100644 --- a/src/ShopNet/Product/Get.ts +++ b/src/ShopNet/Product/Get.ts @@ -124,7 +124,7 @@ export abstract class ProductGet extends ProductData { * @since 1.4.0 * @version 0.3.0 */ - public static usePhotoList(prodId: number, options?: Options) { + public static usePhotoList(prodId: number, options?: Options) { const req = useSWR( this.Index(prodId, this.photoList), (id) => this.PhotoList(id),