feat: add AI Dental Shopping section with sidebar nav and Login Info page

- Add AI Dental Shopping to sidebar with Search/Tag and Login Info sub-pages
- Build full-stack Login Info CRUD: save vendor name, website, username, password per user
- Add ShoppingVendor Prisma model, run db push, regenerate client and Zod schemas
- Add storage layer, REST API at /api/shopping-vendors/, and frontend table with add/edit/delete modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-17 00:35:38 -04:00
parent edec03e893
commit e34140c2b1
217 changed files with 4081 additions and 14 deletions

View File

@@ -24,6 +24,7 @@ import { officeContactStorage } from "./office-contact-storage";
import { procedureTimeslotStorage } from "./procedure-timeslot-storage";
import { insuranceContactStorage } from "./insurance-contact-storage";
import { commissionsStorage } from "./commissions-storage";
import { shoppingVendorStorage } from "./shopping-vendor-storage";
export const storage = {
@@ -51,6 +52,7 @@ export const storage = {
...procedureTimeslotStorage,
...insuranceContactStorage,
...commissionsStorage,
...shoppingVendorStorage,
};

View File

@@ -0,0 +1,37 @@
import { InsertShoppingVendor, ShoppingVendor } from "@repo/db/types";
import { prisma as db } from "@repo/db/client";
export interface IShoppingVendorStorage {
getShoppingVendor(id: number): Promise<ShoppingVendor | null>;
getShoppingVendorsByUser(userId: number): Promise<ShoppingVendor[]>;
createShoppingVendor(data: InsertShoppingVendor): Promise<ShoppingVendor>;
updateShoppingVendor(id: number, updates: Partial<ShoppingVendor>): Promise<ShoppingVendor | null>;
deleteShoppingVendor(userId: number, id: number): Promise<boolean>;
}
export const shoppingVendorStorage: IShoppingVendorStorage = {
async getShoppingVendor(id: number) {
return await db.shoppingVendor.findUnique({ where: { id } }) as ShoppingVendor | null;
},
async getShoppingVendorsByUser(userId: number) {
return await db.shoppingVendor.findMany({ where: { userId }, orderBy: { id: "asc" } }) as ShoppingVendor[];
},
async createShoppingVendor(data: InsertShoppingVendor) {
return await db.shoppingVendor.create({ data }) as ShoppingVendor;
},
async updateShoppingVendor(id: number, updates: Partial<ShoppingVendor>) {
return await db.shoppingVendor.update({ where: { id }, data: updates }) as ShoppingVendor;
},
async deleteShoppingVendor(userId: number, id: number) {
try {
await db.shoppingVendor.delete({ where: { userId, id } });
return true;
} catch {
return false;
}
},
};