diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs b/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs index da69c3a..f9e4d9b 100644 --- a/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs +++ b/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs @@ -24,7 +24,6 @@ public async Task ProductDeletePhoto(uint photoId) { * * Include Types -> Combos * - * * * @author Aloento * @since 0.5.0 @@ -71,7 +70,6 @@ await this.Db.Variants * * Include Combos * - * * * @author Aloento * @since 0.5.0 @@ -185,24 +183,28 @@ public async Task ProductDeleteProduct(uint prodId) { * * @author Aloento * @since 1.2.0 - * @version 0.1.0 + * @version 0.1.1 * */ public async Task ProductDetachCategory(uint prodId) { var prod = await this.Db.Products .SingleAsync(x => x.ProductId == prodId); - if (prod.CategoryId is null) + var cate = prod.CategoryId; + + if (cate is null) return false; + prod.CategoryId = null; + + var res = await this.Db.SaveChangesAsync(); + await this.Db.Categories .Where(x => - x.CategoryId == prod.CategoryId && + x.CategoryId == cate && x.Products.Count == 0) .ExecuteDeleteAsync(); - prod.CategoryId = null; - - return await this.Db.SaveChangesAsync() > 0; + return res > 0; } } diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Post.cs b/SoarCraft.AwaiShop/AdminHub/Product/Post.cs index b9068af..354e83e 100644 --- a/SoarCraft.AwaiShop/AdminHub/Product/Post.cs +++ b/SoarCraft.AwaiShop/AdminHub/Product/Post.cs @@ -78,10 +78,10 @@ public async Task ProductPostMovePhoto(uint photoId, bool up) { * * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.1.1 * */ - public async Task ProductPostPhoto(uint prodId, IAsyncEnumerable input) { + public async Task ProductPostPhoto(uint prodId, IAsyncEnumerable input) { var exist = await this.Db.Products.AnyAsync(x => x.ProductId == prodId); if (!exist) throw new HubException($"Product {prodId} not found"); @@ -95,14 +95,14 @@ public async Task ProductPostPhoto(uint prodId, IAsyncEnumerable i var next = (byte)(await this.Db.Photos.CountAsync(x => x.ProductId == prodId) + 1); - await this.Db.Photos.AddAsync(new() { + var photo = await this.Db.Photos.AddAsync(new() { ProductId = prodId, Object = obj.Entity, Order = next }); await this.Db.SaveChangesAsync(); - return true; + return photo.Entity.PhotoId; } /** diff --git a/SoarCraft.AwaiShop/Hub/Product/Entity.cs b/SoarCraft.AwaiShop/Hub/Product/Entity.cs index b0ad0f3..aa212f3 100644 --- a/SoarCraft.AwaiShop/Hub/Product/Entity.cs +++ b/SoarCraft.AwaiShop/Hub/Product/Entity.cs @@ -56,7 +56,7 @@ internal partial class ShopHub { * * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.1.1 * */ public async Task PhotoEntity(uint key, uint? version) { @@ -69,13 +69,6 @@ internal partial class ShopHub { return await this.Db.Photos .Where(x => x.PhotoId == key) - .Select(x => new { - x.Cover, - x.Caption, - x.Order, - x.ObjectId, - x.Version - }) .SingleOrDefaultAsync(); } diff --git a/src/Pages/Admin/Product/Photo/Edit.tsx b/src/Pages/Admin/Product/Photo/Edit.tsx index edc877c..ef3d901 100644 --- a/src/Pages/Admin/Product/Photo/Edit.tsx +++ b/src/Pages/Admin/Product/Photo/Edit.tsx @@ -45,9 +45,9 @@ const log = new Logger("Admin", "Product", "Detail", "Photo", "Edit"); /** * @author Aloento * @since 0.5.0 - * @version 0.3.3 + * @version 0.3.4 */ -export function AdminProductPhotoEdit({ Photo: { Id, Cover, Caption }, Refresh }: IAdminProductPhotoEdit) { +export function AdminProductPhotoEdit({ Photo: { Id, Cover, Caption, ProductId }, Refresh }: IAdminProductPhotoEdit) { const style = useStyles(); const [cap, setCap] = useState(Caption || ""); @@ -166,7 +166,7 @@ export function AdminProductPhotoEdit({ Photo: { Id, Cover, Caption }, Refresh } Replace - diff --git a/src/Pages/Admin/Product/Photo/index.tsx b/src/Pages/Admin/Product/Photo/index.tsx index 62c09f7..9ff0979 100644 --- a/src/Pages/Admin/Product/Photo/index.tsx +++ b/src/Pages/Admin/Product/Photo/index.tsx @@ -1,7 +1,6 @@ import { Button, DataGridCell, DataGridHeaderCell, Subtitle1, TableColumnDefinition, Toast, ToastTitle, createTableColumn, makeStyles } from "@fluentui/react-components"; import { AddRegular, ArrowDownRegular, ArrowUpRegular } from "@fluentui/react-icons"; -import { useRequest } from "ahooks"; -import { useState } from "react"; +import { useLiveQuery } from "dexie-react-hooks"; import { DelegateDataGrid } from "~/Components/DataGrid"; import { MakeCoverCol } from "~/Helpers/CoverCol"; import { Logger } from "~/Helpers/Logger"; @@ -32,13 +31,15 @@ const log = new Logger("Admin", "Product", "Detail", "Photo"); /** * @author Aloento * @since 0.5.0 - * @version 0.1.0 + * @version 0.2.0 */ export interface IPhotoItem { + /** PhotoId */ Id: number; /** ObjectId */ Cover: string; Caption?: string; + ProductId: number; } /** @@ -107,34 +108,39 @@ const columns: TableColumnDefinition[] = [ * @since 0.5.0 * @version 0.1.0 */ -let refreshCarousel: () => void; +let refreshCarousel: () => void = () => { }; /** * @author Aloento * @since 0.5.0 - * @version 0.4.0 + * @version 0.5.0 */ export function AdminProductPhoto({ ProdId }: { ProdId: number }) { - const [list, setList] = useState([]); - - const { run } = useRequest(() => Hub.Product.Get.PhotoList(ProdId, log), { - onError: log.error, - onSuccess([raw]) { - const map = raw.map(x => ({ - Id: x.Order, - Cover: x.ObjectId, - Caption: x.Caption || "No Caption" - })); - - setList(map); - } + const list = useLiveQuery(async () => { + const [raw] = await Hub.Product.Get.PhotoList(ProdId, log); + + const map = raw.map(x => ({ + Id: x.PhotoId, + Cover: x.ObjectId, + Caption: x.Caption || "No Caption", + ProductId: x.ProductId + })); + + return map; }); - refreshCarousel = run; const { dispatch, dispatchToast } = useErrorToast(log); const { run: newPhoto } = AdminHub.Product.Post.usePhoto(log, { manual: true, + onBefore([prodId, file]) { + dispatchToast( + + Uploading Photo {file.name} for Product {prodId} + , + { intent: "info" } + ); + }, onError(e, params) { dispatch({ Message: "Failed Upload Photo", @@ -149,8 +155,6 @@ export function AdminProductPhoto({ ProdId }: { ProdId: number }) { , { intent: "success" } ); - - run(); } }); diff --git a/src/ShopNet/Admin/Product/Delete.ts b/src/ShopNet/Admin/Product/Delete.ts index 6bc3224..9a6cdbc 100644 --- a/src/ShopNet/Admin/Product/Delete.ts +++ b/src/ShopNet/Admin/Product/Delete.ts @@ -1,6 +1,7 @@ import { useRequest } from "ahooks"; import { Options } from "ahooks/lib/useRequest/src/types"; import { ProductData } from "~/ShopNet/Product/Data"; +import { ProductGet } from "~/ShopNet/Product/Get"; import { AdminNet } from "../AdminNet"; import { AdminProductGet } from "./Get"; @@ -13,12 +14,14 @@ export abstract class AdminProductDelete extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 0.2.0 + * @version 0.3.0 */ - public static usePhoto(options: Options) { - return useRequest(async photoId => { + public static usePhoto(options: Options) { + return useRequest(async (prodId, photoId) => { const res = await this.Invoke("ProductDeletePhoto", photoId); this.EnsureTrue(res); + + ProductGet.PhotoListUpdate(prodId, x => x!.filter(x => x !== photoId)); return res; }, options); } diff --git a/src/ShopNet/Admin/Product/Post.ts b/src/ShopNet/Admin/Product/Post.ts index fc0577d..29de9a5 100644 --- a/src/ShopNet/Admin/Product/Post.ts +++ b/src/ShopNet/Admin/Product/Post.ts @@ -4,6 +4,7 @@ import { Options } from "ahooks/lib/useRequest/src/types"; import { Subject } from "rxjs"; import { Logger } from "~/Helpers/Logger"; import { CurrentEditor } from "~/Lexical/Utils"; +import { ProductGet } from "~/ShopNet/Product/Get"; import { AdminNet } from "../AdminNet"; import { AdminProductGet } from "./Get"; @@ -45,9 +46,9 @@ export abstract class AdminProductPost extends AdminNet { /** * @author Aloento * @since 0.5.0 - * @version 1.0.3 + * @version 1.0.5 */ - public static usePhoto(pLog: Logger, options: Options) { + public static usePhoto(pLog: Logger, options: Options) { const log = useConst(() => pLog.With(...this.Log, "Photo")); return useRequest(async (prodId, file) => { @@ -58,11 +59,13 @@ export abstract class AdminProductPost extends AdminNet { throw new RangeError("File is too large, max 10MB"); const subject = new Subject(); - const res = this.Invoke("ProductPostPhoto", prodId, subject); + const res = this.Invoke("ProductPostPhoto", prodId, subject); await this.HandleFileStream(file, subject, log); - this.EnsureTrue(await res); - return true as const; + const id = await res; + ProductGet.PhotoListUpdate(prodId, x => [...x, id]); + + return id; }, options); } diff --git a/src/ShopNet/Product/Data.ts b/src/ShopNet/Product/Data.ts index 77ace31..41296f7 100644 --- a/src/ShopNet/Product/Data.ts +++ b/src/ShopNet/Product/Data.ts @@ -1,11 +1,6 @@ import { IConcurrency } from "../Database"; import { ShopNet } from "../ShopNet"; -/** - * @author Aloento - * @since 1.3.0 - * @version 0.1.0 - */ export namespace ProductData { export type Product = { Name: string; @@ -17,10 +12,12 @@ export namespace ProductData { } & IConcurrency; export type Photo = { + PhotoId: number; Cover?: boolean; Caption?: string; Order: number; ObjectId: string; + ProductId: number; } & IConcurrency; export type Type = { diff --git a/src/ShopNet/Product/Get.ts b/src/ShopNet/Product/Get.ts index ad53ce1..75e38f2 100644 --- a/src/ShopNet/Product/Get.ts +++ b/src/ShopNet/Product/Get.ts @@ -1,3 +1,4 @@ +import dayjs from "dayjs"; import type { Logger } from "~/Helpers/Logger"; import { IComboItem } from "~/Pages/Admin/Product/Combo"; import type { IGallery } from "~/Pages/Gallery"; @@ -106,6 +107,8 @@ export abstract class ProductGet extends ProductData { return this.GetTimeCache(prodId, "ProductGetComboList", (x) => x.add(1, "m"), prodId); } + public static readonly photoList = "ProductGetPhotoList"; + /** * @author Aloento * @since 1.0.0 @@ -115,7 +118,7 @@ export abstract class ProductGet extends ProductData { public static async PhotoList(prodId: number, pLog: Logger): Promise<[ProductData.Photo[], string]> { const log = pLog.With(...this.Log, "PhotoList"); - const ids = await this.GetTimeCache(prodId, "ProductGetPhotoList", (x) => x.add(1, "m"), prodId).catch(log.error); + const ids = await this.GetTimeCache(prodId, this.photoList, (x) => x.add(1, "m"), prodId).catch(log.error); let list = []; let cover = ""; @@ -140,4 +143,7 @@ export abstract class ProductGet extends ProductData { return [list, cover]; } + public static PhotoListUpdate(prodId: number, action: (list: number[]) => number[]) { + return this.UpdateCache(action, prodId, this.photoList, dayjs().add(1, "m")); + } }