diff --git a/apps/api/src/modules/inventory/repositories/inventory-item-repository.js b/apps/api/src/modules/inventory/repositories/inventory-item-repository.js index 1e52256..b8a1675 100644 --- a/apps/api/src/modules/inventory/repositories/inventory-item-repository.js +++ b/apps/api/src/modules/inventory/repositories/inventory-item-repository.js @@ -25,6 +25,16 @@ export function createInventoryItemRepository(db) { .orderBy(asc(inventoryItems.createdAt)); }, + async listBySellerId(sellerId) { + return db + .select({ item: inventoryItems }) + .from(inventoryItems) + .innerJoin(products, eq(products.id, inventoryItems.productId)) + .where(eq(products.sellerId, sellerId)) + .orderBy(asc(inventoryItems.createdAt)) + .then((rows) => rows.map(({ item }) => item)); + }, + async searchPublic({ page = 1, limit = 20 } = {}) { const conditions = [ eq(inventoryItems.status, 'ACTIVE'), diff --git a/apps/api/src/modules/inventory/routes/admin-inventory-routes.js b/apps/api/src/modules/inventory/routes/admin-inventory-routes.js index f6eec71..d15cd73 100644 --- a/apps/api/src/modules/inventory/routes/admin-inventory-routes.js +++ b/apps/api/src/modules/inventory/routes/admin-inventory-routes.js @@ -54,4 +54,8 @@ export async function registerAdminInventoryRoutes(app, options) { return reply.code(201).send({ data: toAdminInventoryItemDto(inventoryItem) }); }); + + app.get('/inventory', async (request) => ({ + data: (await inventoryItemService.listBySellerId(request.admin.sellerId)).map(toAdminInventoryItemDto), + })); } \ No newline at end of file diff --git a/apps/api/src/modules/inventory/services/inventory-item-service.js b/apps/api/src/modules/inventory/services/inventory-item-service.js index ec2ab15..c46d4e8 100644 --- a/apps/api/src/modules/inventory/services/inventory-item-service.js +++ b/apps/api/src/modules/inventory/services/inventory-item-service.js @@ -7,6 +7,10 @@ export function createInventoryItemService({ productRepository, }) { return { + async listBySellerId(sellerId) { + return inventoryItemRepository.listBySellerId(sellerId); + }, + async create(input) { const inventoryItem = createInventoryItem(input); const [product, condition] = await Promise.all([ diff --git a/apps/web/src/assets/styles/global.scss b/apps/web/src/assets/styles/global.scss index 3aaa876..1b960b9 100644 --- a/apps/web/src/assets/styles/global.scss +++ b/apps/web/src/assets/styles/global.scss @@ -51,6 +51,14 @@ h1 { max-width: 760px; margin: var(--space-3) 0 var(--space-4); font-size: var(- .admin-sidebar__footer { display: grid; gap: var(--space-3); align-self: end; color: var(--color-text-muted); font-size: var(--text-sm); } .admin-shell__content { padding: var(--space-12); } .admin-page { max-width: 880px; } +.admin-workspace-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); margin: var(--space-8) 0; } +.admin-workspace-grid form { display: grid; gap: var(--space-3); } +.admin-list { margin-top: var(--space-8); } +.admin-list h2 { margin-bottom: var(--space-3); font-size: var(--text-xl); } +.admin-list ul { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; } +.admin-list li { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; border-bottom: var(--border-thin) solid var(--color-border); padding: var(--space-3) 0; } +.admin-list li span { color: var(--color-text-muted); font-size: var(--text-sm); } +.admin-list a { color: var(--color-interactive-text); font-weight: var(--weight-bold); } .showcase-header { display: flex; align-items: end; justify-content: space-between; gap: var(--space-8); margin-bottom: var(--space-12); } .showcase-header h1 { max-width: 720px; } .showcase-header p { max-width: 600px; color: var(--color-text-muted); } @@ -109,4 +117,5 @@ h1 { max-width: 760px; margin: var(--space-3) 0 var(--space-4); font-size: var(- .admin-shell { grid-template-columns: 1fr; } .admin-sidebar { border-right: 0; border-bottom: var(--border-thin) solid var(--color-border); } .admin-shell__content { padding: var(--space-6); } + .admin-workspace-grid { grid-template-columns: 1fr; } } diff --git a/apps/web/src/pages/AdminHomePage.vue b/apps/web/src/pages/AdminHomePage.vue index 4bb402f..e602985 100644 --- a/apps/web/src/pages/AdminHomePage.vue +++ b/apps/web/src/pages/AdminHomePage.vue @@ -1,3 +1,91 @@ + + diff --git a/apps/web/src/services/admin-catalog-service.js b/apps/web/src/services/admin-catalog-service.js new file mode 100644 index 0000000..d28f834 --- /dev/null +++ b/apps/web/src/services/admin-catalog-service.js @@ -0,0 +1,23 @@ +const apiBaseUrl = import.meta.env?.VITE_API_BASE_URL ?? '/api/v1'; + +async function request(path, options = {}) { + const response = await fetch(`${apiBaseUrl}${path}`, { + credentials: 'include', + ...options, + headers: { 'content-type': 'application/json', ...options.headers }, + }); + const payload = await response.json().catch(() => null); + if (!response.ok) throw new Error(payload?.error?.message ?? 'Admin catalog request failed.'); + return payload?.data; +} + +export function listAdminProducts() { return request('/admin/products'); } +export function listAdminInventory() { return request('/admin/inventory'); } + +export function createAdminProduct(input, csrfToken) { + return request('/admin/products', { method: 'POST', headers: { 'x-csrf-token': csrfToken }, body: JSON.stringify(input) }); +} + +export function createAdminInventory(input, csrfToken) { + return request('/admin/inventory', { method: 'POST', headers: { 'x-csrf-token': csrfToken }, body: JSON.stringify(input) }); +} \ No newline at end of file