The /purchaser/factories endpoint returns CompanyRelationship objects
(factory_company_id, factory_name, factory_type) but the UI expected
Company objects (id, name). Now maps and filters by factory_type:
- NewPlan: shows only textile factories
- NewWashingPlan: shows only washing factories
Co-Authored-By: Claude <noreply@anthropic.com>
The page called a non-existent /purchaser/products/yarn-ratios endpoint.
When that 404'd, the unhandled error prevented setLoading(false) from running,
leaving the product selector stuck on "加载中". Now extracts yarn_ratios
directly from the product list response (already included in each product object).
Co-Authored-By: Claude <noreply@anthropic.com>
When products have no inventory records, the backend returns {"code":0,"data":null}.
getRecords() resolved with null, causing records.filter() to throw TypeError,
which crashed the entire fetchData() and left the product list empty.
Co-Authored-By: Claude <noreply@anthropic.com>
Both components had raw fetch to /api/v1/common/upload without the
Authorization header. Replace with commonApi.uploadFile which handles
token refresh and auth headers.
Co-Authored-By: Claude <noreply@anthropic.com>
Add proactive token refresh when getAccessToken() returns null, plus
401 retry logic. The in-memory token can be null if it expired between
page load and upload attempt.
Co-Authored-By: Claude <noreply@anthropic.com>
- Add uploadFile function to commonApi with Authorization header
- Replace raw fetch with commonApi.uploadFile in ImageUploader
Co-Authored-By: Claude <noreply@anthropic.com>
Frontend was sending `type` and `target_id` but backend expects
`resource_type` and `resource_id`, causing 40000 validation error.
Co-Authored-By: Claude <noreply@anthropic.com>
- DemoDisclaimerModal: remove bing redirect on "稍后再说", just close
- App.tsx: remove will-change:transform from AnimatedRoute which created
a new containing block and broke fixed positioning for all modals
- OnboardingGuide: add Portal as extra safety for centering
Co-Authored-By: Claude <noreply@anthropic.com>
AnimatedRoute's will-change:transform creates a new containing block,
which makes fixed positioning relative to that element instead of the
viewport. When the page is scrolled, the modal appears off-center.
Using createPortal to render at document.body bypasses this issue and
ensures the modal always appears centered in the viewport.
Co-Authored-By: Claude <noreply@anthropic.com>
Initialize hasRelationship and isCheckingRelationship based on whether
factoryId is present, so the first render frame already shows the correct
content instead of an empty backdrop overlay.
Co-Authored-By: Claude <noreply@anthropic.com>
When clicking "分享链接" for unlinked plans, factoryId is undefined.
The ShareModal's useEffect returned early without setting hasRelationship,
leaving it as null — none of the three conditional branches rendered,
showing only the backdrop overlay with no content.
Fix: when factoryId is missing, set hasRelationship=false directly to
show the share link generation UI. Also remove the factoryId guard from
generateShortLink since it only needs planId and factoryType.
Co-Authored-By: Claude <noreply@anthropic.com>
JS files were output as main.js without content hash, combined with
nginx's 30-day immutable cache. Browsers would never fetch updated code.
- Add [contenthash:8] to webpack output filenames in production
- Set index.html to no-cache so browsers always check for new bundles
- Only apply immutable cache to hashed filenames (*.abcdef01.js)
Co-Authored-By: Claude <noreply@anthropic.com>
The /purchaser/plans API returns data as a plain array, but usePlanData
destructured res.data expecting {plans, factories, plan_factories, operator_names}.
This caused the PlanOverview page to render nothing despite receiving data.
Co-Authored-By: Claude <noreply@anthropic.com>
The purchaser plans endpoint returns data as a plain array, not wrapped
in {plans:[...]}. Added Array.isArray check to handle both formats.
Co-Authored-By: Claude <noreply@anthropic.com>
When API returns {"data": null} for empty lists, accessing .plans on
null throws an exception. fetchPlans never reaches setLoading(false),
causing the dashboard to spin forever. Added optional chaining,
fallback defaults, and try-catch-finally to ensure loading stops.
Co-Authored-By: Claude <noreply@anthropic.com>
Dashboard pages hung on "加载仪表盘数据..." when auth.company was null
(e.g. admin user). Now sets loading=false immediately when no company
is present, so the page renders instead of spinning forever.
Co-Authored-By: Claude <noreply@anthropic.com>
The refresh endpoint expects {"refresh_token":"..."} in the POST body,
but the frontend was sending an empty body (EOF error). Now stores the
refresh_token from login response and sends it on refresh requests.
Co-Authored-By: Claude <noreply@anthropic.com>
Add a prominent green banner at the top of the login page to visually
verify that the GitOps CI/CD pipeline auto-deploys successfully.
Co-Authored-By: Claude <noreply@anthropic.com>
Copy .npmrc for registry mirror, set NODE_ENV=development during
npm ci to ensure webpack (devDep) is installed, use npx webpack
for build step.
Co-Authored-By: Claude <noreply@anthropic.com>
Docker Hub unreachable from k3s cluster. Switch Dockerfile to pull
node and nginx base images from Harbor registry.
Co-Authored-By: Claude <noreply@anthropic.com>
Set DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH for k8s runner
DinD sidecar communication. Add 'until docker info' wait loop to
ensure daemon is ready before build starts.
Co-Authored-By: Claude <noreply@anthropic.com>
Multi-stage Docker build: node:20-alpine + nginx:1.27-alpine.
CI builds and pushes to harbor-in-k3s.cheverjohn.me/iloom/iloom-frontend.
Co-Authored-By: Claude <noreply@anthropic.com>
WHY: migrating from Supabase BaaS to self-hosted iloom backend
(Go microservices) deployed on K3s. Supabase client and realtime
subscriptions no longer applicable.
HOW:
- add src/api/ layer (client.ts, auth.ts, purchaser.ts, textile.ts,
washing.ts, websocket.ts) targeting iloom-gateway REST endpoints
- rewrite all hooks (usePlanData, useInventoryData, useDashboardData,
useRealtime, etc.) to use new API client instead of Supabase queries
- rewrite all page/component data fetching and mutations accordingly
- delete src/supabase/client.ts, remove @supabase/supabase-js dep
- add Dockerfile (multi-stage node build + nginx) and nginx.conf
with reverse proxy to iloom-gateway for /api/ and /ws/ routes
- adjust webpack.config.js for API_URL environment variable injection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>