From ec9f2d9816f67afbda6e611a5d1b1f62dc9fdf2a Mon Sep 17 00:00:00 2001 From: seb Date: Mon, 2 Nov 2026 00:53:48 +0100 Subject: [PATCH] u --- .agents/skills/backend/SKILL.md | 91 ++++++++++--------- src/automats/authAutomat.js | 2 +- .../settings/MembersSettingsPanel.jsx | 25 ++++- 3 files changed, 70 insertions(+), 48 deletions(-) diff --git a/.agents/skills/backend/SKILL.md b/.agents/skills/backend/SKILL.md index a88a551..c09739c 100644 --- a/.agents/skills/backend/SKILL.md +++ b/.agents/skills/backend/SKILL.md @@ -140,17 +140,17 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). ### Reusable types -- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T20[], externalNumber?: str|null, createdAt?: str, shipping?: T23|null, shippings?: T23[], payment?: T32|null, payments?: T32[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T10[] }` +- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T20[], externalNumber?: str|null, createdAt?: str, shipping?: T23|null, shippings?: T23[], payment?: T33|null, payments?: T33[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T10[] }` - `T2` = `{ id: int, number: str, type: str, date: str, lines: T20[], shippings: T23[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T10[], state?: str }` - `T3` = `{ id: int, number: str, type: str, date: str, lines: T20[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T10[] }` -- `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T24[], files: T10[], externalNumber?: str|null, createdAt?: str, ledger?: T39 }` +- `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T24[], files: T10[], externalNumber?: str|null, createdAt?: str, ledger?: T40 }` - `T5` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T6|null }` - `T6` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T11, totalSizeBytes: int, durationMs: int }` - `T7` = `{ id: int, name: str, pairingpin?: str|null, token?: str|null, pairedAt?: str|null, clientserial?: str|null, rootCategoryId?: int|null, syncRootArticles?: bool, sort?: int, enabled?: bool, clientversion?: str|null, lastseenAt?: str|null, updatedAt?: str|null, categoryIds?: int[] }` -- `T8` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T41|null, incoming?: { date: str, amount: num }[] }` +- `T8` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T42|null, incoming?: { date: str, amount: num }[] }` - `T9` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T25[] }, thumb?: str|null }` - `T10` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }` -- `T11` = `{ vfs: T40, storage: T40, database: T34 }` +- `T11` = `{ vfs: T41, storage: T41, database: T35 }` - `T12` = `{ id: int, name: str, companyId?: int, token?: str|null, pairingpin?: str|null, pairedAt?: str|null, agentversion?: str|null, lastseenAt?: str|null, updatedAt?: str, sort?: int, enabled?: bool }` - `T13` = `{ id: int, printeragentId: int, type: str, enabled: bool, sort: int, model?: str|null, serial?: str|null, open?: bool, pause?: bool, error?: bool, online?: bool, labelLength?: int|null }` - `T14` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` @@ -170,17 +170,18 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). - `T28` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }` - `T29` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` - `T30` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }` -- `T31` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }` -- `T32` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }` -- `T33` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }` -- `T34` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }` -- `T35` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }` -- `T36` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` -- `T37` = `{ id: int, entityId: int, contactType: str, contact: str }` -- `T38` = `{ id: int, name: str, createdAt: str, updatedAt: str }` -- `T39` = `{ totalPositions: int, completelyOnLedgerPositions: int }` -- `T40` = `{ name: str, sizeBytes: int, sourceExists: bool }` -- `T41` = `{ net: num, taxId: int, taxRate?: num|null }` +- `T31` = `{ id: int, googleAuthId: int, email: str, role: str, picture?: str|null }` +- `T32` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }` +- `T33` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }` +- `T34` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }` +- `T35` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }` +- `T36` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }` +- `T37` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` +- `T38` = `{ id: int, entityId: int, contactType: str, contact: str }` +- `T39` = `{ id: int, name: str, createdAt: str, updatedAt: str }` +- `T40` = `{ totalPositions: int, completelyOnLedgerPositions: int }` +- `T41` = `{ name: str, sizeBytes: int, sourceExists: bool }` +- `T42` = `{ net: num, taxId: int, taxRate?: num|null }` ### Routes @@ -387,25 +388,25 @@ _none_ - Returns: `200 { attributevalue: T30 }` #### Delete attribute value image — `DELETE /attributevalue/{id}/image` - Params: `id: int` -- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T36[] } }` +- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T37[] } }` #### Add or replace attribute value image — `POST /attributevalue/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T36[] } }` +- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T37[] } }` ### Section: /auth #### Login with Google ID token — `POST /auth/login` - Params: `body: { idToken: str }` -- Returns: `200 { message: str, user: T35 }` +- Returns: `200 { message: str, user: T36 }` #### Logout — `POST /auth/logout` _none_ - Returns: `200 { message: str }` #### Current user — `GET /auth/me` _none_ -- Returns: `200 { user: T35, googleClientId: str, schemaVersion: int, vhost: bool, vhostSettings: str }` +- Returns: `200 { user: T36, googleClientId: str, schemaVersion: int, vhost: bool, vhostSettings: str }` #### Set selected company — `PUT /auth/selected-company` - Params: `body: { companyId: int }` -- Returns: `200 { user: T35 }` +- Returns: `200 { user: T36 }` ### Section: /autoinvite @@ -438,7 +439,7 @@ _none_ - Returns: `200 { brands: { id: int, name: str, articleCount: int, pics: { fullHash: str, thumbW: int, thumbH: int, thumbHash: str, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null }[], gsprpinfo?: str|null }[], total: int, offset: int, limit: int }` #### Create brand — `POST /brand` - Params: `body: { name: str, gsprpinfo?: str }` -- Returns: `201 { brand: { id: int, name: str, pics: T36[], gsprpinfo?: str|null } }` +- Returns: `201 { brand: { id: int, name: str, pics: T37[], gsprpinfo?: str|null } }` #### Delete brand — `DELETE /brand/{id}` - Params: `id: int` - Returns: `204 void` @@ -447,19 +448,19 @@ _none_ - Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` -- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T36[] } }` +- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T37[] } }` #### Add or replace brand image — `POST /brand/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T36[] } }` +- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T37[] } }` ### Section: /carrier #### List carriers — `GET /carrier` _none_ -- Returns: `200 { carriers: T33[] }` +- Returns: `200 { carriers: T34[] }` #### Create carrier — `POST /carrier` - Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }` -- Returns: `201 { carrier: T33 }` +- Returns: `201 { carrier: T34 }` #### Reorder carriers — `PUT /carrier/order` - Params: `body: { carrierIds: int[] }` - Returns: `200 { carrierIds: int[] }` @@ -468,10 +469,10 @@ _none_ - Returns: `204 void` #### Get carrier by ID — `GET /carrier/{id}` - Params: `id: int` -- Returns: `200 { carrier: T33 }` +- Returns: `200 { carrier: T34 }` #### Update carrier — `PATCH /carrier/{id}` - Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }` -- Returns: `200 { carrier: T33 }` +- Returns: `200 { carrier: T34 }` ### Section: /category @@ -528,7 +529,7 @@ _none_ - Returns: `200 { companies: T19[] }` #### Create company — `POST /company` - Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }` -- Returns: `201 { company: T19, user: T35 }` +- Returns: `201 { company: T19, user: T36 }` #### Export company catalog as zip archive (SSE) — `GET /company/export` - Params: `query: stream?: str` - Returns: `200 SSE` @@ -564,16 +565,16 @@ _none_ #### List company users — `GET /companyuser` _none_ -- Returns: `200 { companyusers: { id: int, googleAuthId: int, email: str, role: str }[] }` +- Returns: `200 { companyusers: T31[] }` #### Add an existing user to the company — `POST /companyuser` - Params: `body: { email: str, role?: str }` -- Returns: `201 { companyuser: { id: int, googleAuthId: int, email: str, role: str } }` +- Returns: `201 { companyuser: T31 }` #### Remove a user from the company — `DELETE /companyuser/{id}` - Params: `id: int` - Returns: `204 void` #### Change a company user's role — `PATCH /companyuser/{id}` - Params: `id: int, body: { role: str }` -- Returns: `200 { companyuser: { id: int, googleAuthId: int, role: str } }` +- Returns: `200 { companyuser: T31 }` ### Section: /customergroup @@ -597,7 +598,7 @@ _none_ #### List deliveries — `GET /delivery` - Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int, seekMonths?: int` -- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T39 }[], total: int, unfilteredTotal: int, offset: int, limit: int }` +- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T40 }[], total: int, unfilteredTotal: int, offset: int, limit: int }` #### Create delivery — `POST /delivery` - Params: `body: { externalNumber: str, date: str, supplierId: int, lines: { amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` - Returns: `201 { delivery: T4 }` @@ -645,16 +646,16 @@ _none_ - Returns: `200 { address: T14 }` #### List entity contacts — `GET /entity/{entityId}/contact` - Params: `entityId: int` -- Returns: `200 { contacts: T37[] }` +- Returns: `200 { contacts: T38[] }` #### Create entity contact — `POST /entity/{entityId}/contact` - Params: `entityId: int, body: { contactType: str, contact: str }` -- Returns: `201 { contact: T37 }` +- Returns: `201 { contact: T38 }` #### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}` - Params: `entityId: int, id: int` - Returns: `204 void` #### Update entity contact — `PATCH /entity/{entityId}/contact/{id}` - Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }` -- Returns: `200 { contact: T37 }` +- Returns: `200 { contact: T38 }` #### Delete entity — `DELETE /entity/{id}` - Params: `id: int` - Returns: `204 void` @@ -846,10 +847,10 @@ _none_ - Returns: `200 str` #### List payments of an order — `GET /order/{id}/payment` - Params: `id: int` -- Returns: `200 { payments: T32[] }` +- Returns: `200 { payments: T33[] }` #### Add payment to an order — `POST /order/{id}/payment` - Params: `id: int, body: { accountId: int, type: str, amount: num }` -- Returns: `201 { payment: T32 }` +- Returns: `201 { payment: T33 }` #### Generate and attach order PDF — `POST /order/{id}/pdf` - Params: `id: int` - Returns: `201 { attachment: T10, fileUrl: str }` @@ -975,10 +976,10 @@ _none_ #### List property names — `GET /propertyname` _none_ -- Returns: `200 { propertynames: T31[] }` +- Returns: `200 { propertynames: T32[] }` #### Create property name — `POST /propertyname` - Params: `body: { name: str, sort?: int, exclusive?: bool }` -- Returns: `201 { propertyname: T31 }` +- Returns: `201 { propertyname: T32 }` #### Reorder property names — `PUT /propertyname/order` - Params: `body: { propertynameIds: int[] }` - Returns: `200 { ok: bool }` @@ -987,7 +988,7 @@ _none_ - Returns: `200 { ok: bool }` #### Update property name — `PATCH /propertyname/{id}` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` -- Returns: `200 { propertyname: T31 }` +- Returns: `200 { propertyname: T32 }` ### Section: /public @@ -1182,22 +1183,22 @@ _none_ #### List suppliers — `GET /supplier` _none_ -- Returns: `200 { suppliers: T38[] }` +- Returns: `200 { suppliers: T39[] }` #### Create supplier — `POST /supplier` - Params: `body: { name: str }` -- Returns: `201 { supplier: T38 }` +- Returns: `201 { supplier: T39 }` #### Delete supplier — `DELETE /supplier/{id}` - Params: `id: int` - Returns: `204 void` #### Update supplier — `PATCH /supplier/{id}` - Params: `id: int, body: { name: str }` -- Returns: `200 { supplier: T38 }` +- Returns: `200 { supplier: T39 }` #### List supplier articles by SKUs — `GET /supplier/{id}/articles` - Params: `id: int, query: skus?: str` -- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T41|null, incoming?: { date: str, amount: num }[] }[] }` +- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T42|null, incoming?: { date: str, amount: num }[] }[] }` #### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles` - Params: `id: int, body?: { skus?: str[] }` -- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T41|null, incoming?: { date: str, amount: num }[] }[] }` +- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T42|null, incoming?: { date: str, amount: num }[] }[] }` ### Section: /thumb diff --git a/src/automats/authAutomat.js b/src/automats/authAutomat.js index 3062b89..f941169 100644 --- a/src/automats/authAutomat.js +++ b/src/automats/authAutomat.js @@ -84,7 +84,7 @@ export function setAuthEventDispatcher(dispatch) { emit = dispatch; } -function normalizePicture(picture) { +export function normalizePicture(picture) { if (!picture || typeof picture !== 'string') return null; if (picture.startsWith('http://') || picture.startsWith('https://') || picture.startsWith('data:')) { return picture; diff --git a/src/components/settings/MembersSettingsPanel.jsx b/src/components/settings/MembersSettingsPanel.jsx index 81ff75b..5f71d5c 100644 --- a/src/components/settings/MembersSettingsPanel.jsx +++ b/src/components/settings/MembersSettingsPanel.jsx @@ -1,4 +1,5 @@ import { PureComponent } from 'react'; +import Avatar from '@mui/material/Avatar'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; @@ -9,8 +10,10 @@ import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutlined'; +import MailOutlinedIcon from '@mui/icons-material/MailOutlined'; import { enqueueSnackbar } from 'notistack'; import companyAutomat, { selectIsCompanyAdmin } from '../../automats/companyAutomat'; +import { normalizePicture } from '../../automats/authAutomat'; import { notifyError } from '../../utils/notify'; import { backendErrorFrom } from '../../utils/backendError'; @@ -157,7 +160,14 @@ export default class MembersSettingsPanel extends PureComponent { {loading && members.length === 0 && } {members.map((m) => ( - + + + {m.email?.[0]?.toUpperCase()} + {m.email} {this.renderRoleSelect(m.role, (role) => this.handleSetRole(m, role), busy)} this.handleRemoveMember(m)}> @@ -192,7 +202,18 @@ export default class MembersSettingsPanel extends PureComponent { Keine offenen Einladungen. )} {invites.map((i) => ( - + + + + {i.email} {ROLES.find((r) => r.value === i.role)?.label ?? i.role} this.handleDeleteInvite(i)}>