feat(resume): admin bulk resume import
Adds the dashboard surface for the backend's /api/admin/v1/resume/import-batches endpoints, and the multipart support it needed. Client: - rawRequest passes a FormData body through untouched and skips Content-Type, so the browser sets multipart/form-data *with the boundary*. Setting it by hand stripped the boundary and the server saw an empty upload — verified against the backend: 202 with the fix, 422 VALIDATION_ERROR without it. This was the first admin endpoint in the codebase to accept a file. - New api.upload(). fetch reports no upload progress, so upload UI stays indeterminate rather than faking a percentage. UI (/resume/import-batches, gated on admin:resume:read; writes on :manage): - Upload dialog with countryCode and dry-run checked by default — seeing the collision report before creating accounts is almost always what you want. - Detail sheet polls every 2s and stops once the batch is terminal. Clickable count tiles filter the per-resume report; step statuses surface `命中缓存` / `复用结果` so the dedup is visible. - Confirm a dry run, retry failed rows, and resolve a missing contact. IMPORT_IDENTITY_TAKEN is shown inline — a collision is expected input, not an error toast. - progress.total is 0 until unpacking finishes, so both views render 准备中… rather than "0 / 0", which would read as an empty batch. types.gen.ts regenerated from the backend's admin spec. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -32,6 +32,7 @@ import { Route as AuthedStudioRobotAuthorsRouteImport } from './routes/_authed/s
|
||||
import { Route as AuthedStudioCategoryBriefsRouteImport } from './routes/_authed/studio/category-briefs'
|
||||
import { Route as AuthedStudioArticlesRouteImport } from './routes/_authed/studio/articles'
|
||||
import { Route as AuthedStudioAgentTokensRouteImport } from './routes/_authed/studio/agent-tokens'
|
||||
import { Route as AuthedResumeImportBatchesRouteImport } from './routes/_authed/resume/import-batches'
|
||||
import { Route as AuthedExcProvidersRouteImport } from './routes/_authed/exc/providers'
|
||||
import { Route as AuthedExcOrdersRouteImport } from './routes/_authed/exc/orders'
|
||||
import { Route as AuthedExcDispatchesRouteImport } from './routes/_authed/exc/dispatches'
|
||||
@@ -158,6 +159,12 @@ const AuthedStudioAgentTokensRoute = AuthedStudioAgentTokensRouteImport.update({
|
||||
path: '/studio/agent-tokens',
|
||||
getParentRoute: () => AuthedRoute,
|
||||
} as any)
|
||||
const AuthedResumeImportBatchesRoute =
|
||||
AuthedResumeImportBatchesRouteImport.update({
|
||||
id: '/resume/import-batches',
|
||||
path: '/resume/import-batches',
|
||||
getParentRoute: () => AuthedRoute,
|
||||
} as any)
|
||||
const AuthedExcProvidersRoute = AuthedExcProvidersRouteImport.update({
|
||||
id: '/exc/providers',
|
||||
path: '/exc/providers',
|
||||
@@ -216,6 +223,7 @@ export interface FileRoutesByFullPath {
|
||||
'/exc/dispatches': typeof AuthedExcDispatchesRoute
|
||||
'/exc/orders': typeof AuthedExcOrdersRoute
|
||||
'/exc/providers': typeof AuthedExcProvidersRoute
|
||||
'/resume/import-batches': typeof AuthedResumeImportBatchesRoute
|
||||
'/studio/agent-tokens': typeof AuthedStudioAgentTokensRoute
|
||||
'/studio/articles': typeof AuthedStudioArticlesRoute
|
||||
'/studio/category-briefs': typeof AuthedStudioCategoryBriefsRoute
|
||||
@@ -247,6 +255,7 @@ export interface FileRoutesByTo {
|
||||
'/exc/dispatches': typeof AuthedExcDispatchesRoute
|
||||
'/exc/orders': typeof AuthedExcOrdersRoute
|
||||
'/exc/providers': typeof AuthedExcProvidersRoute
|
||||
'/resume/import-batches': typeof AuthedResumeImportBatchesRoute
|
||||
'/studio/agent-tokens': typeof AuthedStudioAgentTokensRoute
|
||||
'/studio/articles': typeof AuthedStudioArticlesRoute
|
||||
'/studio/category-briefs': typeof AuthedStudioCategoryBriefsRoute
|
||||
@@ -280,6 +289,7 @@ export interface FileRoutesById {
|
||||
'/_authed/exc/dispatches': typeof AuthedExcDispatchesRoute
|
||||
'/_authed/exc/orders': typeof AuthedExcOrdersRoute
|
||||
'/_authed/exc/providers': typeof AuthedExcProvidersRoute
|
||||
'/_authed/resume/import-batches': typeof AuthedResumeImportBatchesRoute
|
||||
'/_authed/studio/agent-tokens': typeof AuthedStudioAgentTokensRoute
|
||||
'/_authed/studio/articles': typeof AuthedStudioArticlesRoute
|
||||
'/_authed/studio/category-briefs': typeof AuthedStudioCategoryBriefsRoute
|
||||
@@ -313,6 +323,7 @@ export interface FileRouteTypes {
|
||||
| '/exc/dispatches'
|
||||
| '/exc/orders'
|
||||
| '/exc/providers'
|
||||
| '/resume/import-batches'
|
||||
| '/studio/agent-tokens'
|
||||
| '/studio/articles'
|
||||
| '/studio/category-briefs'
|
||||
@@ -344,6 +355,7 @@ export interface FileRouteTypes {
|
||||
| '/exc/dispatches'
|
||||
| '/exc/orders'
|
||||
| '/exc/providers'
|
||||
| '/resume/import-batches'
|
||||
| '/studio/agent-tokens'
|
||||
| '/studio/articles'
|
||||
| '/studio/category-briefs'
|
||||
@@ -376,6 +388,7 @@ export interface FileRouteTypes {
|
||||
| '/_authed/exc/dispatches'
|
||||
| '/_authed/exc/orders'
|
||||
| '/_authed/exc/providers'
|
||||
| '/_authed/resume/import-batches'
|
||||
| '/_authed/studio/agent-tokens'
|
||||
| '/_authed/studio/articles'
|
||||
| '/_authed/studio/category-briefs'
|
||||
@@ -556,6 +569,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthedStudioAgentTokensRouteImport
|
||||
parentRoute: typeof AuthedRoute
|
||||
}
|
||||
'/_authed/resume/import-batches': {
|
||||
id: '/_authed/resume/import-batches'
|
||||
path: '/resume/import-batches'
|
||||
fullPath: '/resume/import-batches'
|
||||
preLoaderRoute: typeof AuthedResumeImportBatchesRouteImport
|
||||
parentRoute: typeof AuthedRoute
|
||||
}
|
||||
'/_authed/exc/providers': {
|
||||
id: '/_authed/exc/providers'
|
||||
path: '/exc/providers'
|
||||
@@ -627,6 +647,7 @@ interface AuthedRouteChildren {
|
||||
AuthedExcDispatchesRoute: typeof AuthedExcDispatchesRoute
|
||||
AuthedExcOrdersRoute: typeof AuthedExcOrdersRoute
|
||||
AuthedExcProvidersRoute: typeof AuthedExcProvidersRoute
|
||||
AuthedResumeImportBatchesRoute: typeof AuthedResumeImportBatchesRoute
|
||||
AuthedStudioAgentTokensRoute: typeof AuthedStudioAgentTokensRoute
|
||||
AuthedStudioArticlesRoute: typeof AuthedStudioArticlesRoute
|
||||
AuthedStudioCategoryBriefsRoute: typeof AuthedStudioCategoryBriefsRoute
|
||||
@@ -657,6 +678,7 @@ const AuthedRouteChildren: AuthedRouteChildren = {
|
||||
AuthedExcDispatchesRoute: AuthedExcDispatchesRoute,
|
||||
AuthedExcOrdersRoute: AuthedExcOrdersRoute,
|
||||
AuthedExcProvidersRoute: AuthedExcProvidersRoute,
|
||||
AuthedResumeImportBatchesRoute: AuthedResumeImportBatchesRoute,
|
||||
AuthedStudioAgentTokensRoute: AuthedStudioAgentTokensRoute,
|
||||
AuthedStudioArticlesRoute: AuthedStudioArticlesRoute,
|
||||
AuthedStudioCategoryBriefsRoute: AuthedStudioCategoryBriefsRoute,
|
||||
|
||||
Reference in New Issue
Block a user