Seven reported items, of which the first four had two root causes. The active tab was invisible in dark mode because colorPrimary was hardcoded to #1a1a1a in both themes. The accent now inverts with the theme, and colorTextLightSolid inverts with it, or a near-white accent would get antd's default white label and disappear. The Category tab, Tag tab, and item-form category selector ignored the theme entirely. antd declares main: lib/index.js and module: es/index.js, so importing from 'antd' resolves to the ES build while 'antd/lib/...' loads the CommonJS one — two copies, two React contexts, and no ConfigProvider for anything deep-imported. Switching those files to antd/es/* keeps the deep-import convention and shares the instance. This was introduced by my own use of the lib path; es is correct under Vite. Two storefront components had the same latent bug. "Colour" is now "Color". The Customers tab shows how many items each customer is holding, as a link opening the item list with a Release button. Release mirrors the customer's own cart removal — drop the cart row, return the item to available, guarded on 'reserved' so it can never resurrect a sold item — and deliberately sends no email about an action the customer did not take. The count is a subquery rather than another join, which would have multiplied rows and inflated order_count and total_spent_cents. The Inventory tab filters by category, tags, price, and status, reusing the storefront's parser and query builder so the two cannot drift. Reserved is one option in a Status filter rather than a standalone toggle. Also fixes two defects the screenshots exposed: the reserved-count link bubbled to the row handler and opened the customer drawer behind the dialog, and .admin-category-node had no CSS at all, so the tree node name, item count, and actions ran together as one string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
145 lines
3.0 KiB
CSS
Executable File
145 lines
3.0 KiB
CSS
Executable File
body { margin: 0; }
|
|
|
|
.item-card .card-cover-img { width: 100%; height: 220px; object-fit: cover; display: block; }
|
|
.item-card .card-cover-placeholder { width: 100%; height: 220px; background: #eee; }
|
|
.price { font-weight: 600; margin: 8px 0; font-size: 16px; }
|
|
.paypal-slot { margin-top: 8px; }
|
|
|
|
.carousel-wrap { position: relative; }
|
|
.carousel-arrow {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
z-index: 2;
|
|
opacity: 0.85;
|
|
}
|
|
.carousel-arrow-left { left: 8px; }
|
|
.carousel-arrow-right { right: 8px; }
|
|
.carousel-count {
|
|
position: absolute;
|
|
bottom: 8px;
|
|
right: 8px;
|
|
background: rgba(0,0,0,0.6);
|
|
color: #fff;
|
|
font-size: 11px;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
z-index: 2;
|
|
}
|
|
|
|
.markdown-body { font-size: 14px; }
|
|
.markdown-body p { margin: 4px 0; }
|
|
.markdown-body ul, .markdown-body ol { margin: 4px 0; padding-left: 20px; }
|
|
.markdown-body h1, .markdown-body h2, .markdown-body h3 { font-size: 15px; margin: 8px 0 4px; }
|
|
|
|
/* Site header — wraps to two rows on narrow viewports instead of overflowing */
|
|
.site-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: 8px 16px;
|
|
height: auto;
|
|
min-height: 64px;
|
|
padding: 12px 24px;
|
|
line-height: normal;
|
|
}
|
|
|
|
.site-header-title {
|
|
margin: 0 !important;
|
|
font-size: clamp(16px, 5vw, 24px);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
}
|
|
|
|
.site-header-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.site-header {
|
|
padding: 10px 12px;
|
|
}
|
|
.site-header-actions .ant-btn {
|
|
padding-left: 10px;
|
|
padding-right: 10px;
|
|
font-size: 13px;
|
|
}
|
|
}
|
|
/* Filter bar: the Filters button sits inline with the active-filter chips on
|
|
desktop; on a narrow screen the chips wrap onto their own line beneath it. */
|
|
.filter-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.active-filter-chips {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
}
|
|
|
|
.active-filter-chips .ant-tag {
|
|
margin-inline-end: 0;
|
|
}
|
|
|
|
.item-category {
|
|
display: block;
|
|
font-size: 12px;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.item-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px 0;
|
|
margin: 8px 0 4px;
|
|
}
|
|
|
|
@media (max-width: 575px) {
|
|
.filter-bar {
|
|
align-items: flex-start;
|
|
}
|
|
.filter-bar > .ant-btn {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
/* Admin inventory filter row — always visible above the table, wrapping onto
|
|
further lines on narrow screens rather than scrolling horizontally. */
|
|
.inventory-filters {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
/* Admin category tree node: name, item count, then actions pushed to the
|
|
right. Without this the three run together as one unbroken string. */
|
|
.admin-category-node {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
}
|
|
|
|
.admin-category-node > span:first-child {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.admin-category-node .ant-space {
|
|
margin-left: auto;
|
|
}
|