style(batch): FROM 카드 행 그룹화 + 컴팩트 폰트로 sparse 레이아웃 정리

이전: API 서버 URL / 인증 토큰 / 엔드포인트 / 메서드 / 데이터 배열 경로가 각각 단독 행
신규: 의미 단위로 그룹화 + 컴팩트화

- API 서버 URL (3fr) + HTTP 메서드 (1fr) 한 행
- 엔드포인트 + 데이터 배열 경로 50:50 한 행
- 인증 토큰: 라디오 → 세그먼티드 토글 버튼 그룹 + 입력을 한 행에 압축
- Label text-xs / Input h-9 text-sm 로 컴팩트 통일

vexplor_rps batch-management-new (L1417 부근) 의 컴팩트 레이아웃 패턴 참고.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hjjeong
2026-05-13 14:01:40 +09:00
parent 0c9e22a679
commit d02bc38f6c
@@ -806,7 +806,7 @@ export default function BatchManagementNewPage() {
<div className="space-y-4">
{/* 등록된 연결 선택 — 외부 커넥션 관리에 등록한 REST API 연결을 골라 자동 호출 */}
<div>
<Label htmlFor="registeredRestApi" className="flex items-center gap-1.5">
<Label htmlFor="registeredRestApi" className="flex items-center gap-1.5 text-xs">
<span>🔗 </span>
{rawResponseLoading && (
<RefreshCw className="h-3 w-3 animate-spin text-muted-foreground" />
@@ -816,7 +816,7 @@ export default function BatchManagementNewPage() {
value={selectedRestApiId}
onValueChange={applyRegisteredRestApi}
>
<SelectTrigger id="registeredRestApi">
<SelectTrigger id="registeredRestApi" className="h-9 text-sm">
<SelectValue placeholder="직접 입력 (등록된 연결 사용 안 함)" />
</SelectTrigger>
<SelectContent>
@@ -838,125 +838,96 @@ export default function BatchManagementNewPage() {
)}
</div>
{/* API 서버 URL */}
<div>
<Label htmlFor="fromApiUrl">API URL *</Label>
<Input
id="fromApiUrl"
value={fromApiUrl}
onChange={(e) => setFromApiUrl(e.target.value)}
placeholder="https://api.example.com"
/>
</div>
{/* 인증 토큰 설정 */}
<div>
<Label> (Authorization)</Label>
{/* 토큰 설정 방식 선택 */}
<div className="mt-2 flex gap-4">
<label className="flex cursor-pointer items-center gap-1.5">
<input
type="radio"
name="authTokenMode"
value="direct"
checked={authTokenMode === "direct"}
onChange={() => {
setAuthTokenMode("direct");
setAuthServiceName("");
}}
className="h-3.5 w-3.5"
/>
<span className="text-xs"> </span>
</label>
<label className="flex cursor-pointer items-center gap-1.5">
<input
type="radio"
name="authTokenMode"
value="db"
checked={authTokenMode === "db"}
onChange={() => setAuthTokenMode("db")}
className="h-3.5 w-3.5"
/>
<span className="text-xs">DB에서 </span>
</label>
</div>
{/* 직접 입력 모드 */}
{authTokenMode === "direct" && (
{/* API 서버 URL + HTTP 메서드 — 한 행, URL 이 길어 7:3 비율 */}
<div className="grid grid-cols-[3fr_1fr] gap-3">
<div>
<Label htmlFor="fromApiUrl" className="text-xs">API URL *</Label>
<Input
id="fromApiKey"
value={fromApiKey}
onChange={(e) => setFromApiKey(e.target.value)}
placeholder="Bearer eyJhbGciOiJIUzI1NiIs..."
className="mt-2"
id="fromApiUrl"
value={fromApiUrl}
onChange={(e) => setFromApiUrl(e.target.value)}
placeholder="https://api.example.com"
className="h-9 text-sm"
/>
)}
{/* DB 선택 모드 */}
{authTokenMode === "db" && (
<Select
value={authServiceName || "none"}
onValueChange={(value) => setAuthServiceName(value === "none" ? "" : value)}
>
<SelectTrigger className="mt-2">
<SelectValue placeholder="서비스명 선택" />
</div>
<div>
<Label className="text-xs"></Label>
<Select value={fromApiMethod} onValueChange={(value: any) => setFromApiMethod(value)}>
<SelectTrigger className="h-9 text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{authServiceNames.map((name) => (
<SelectItem key={name} value={name}>
{name}
</SelectItem>
))}
<SelectItem value="GET">GET</SelectItem>
<SelectItem value="POST">POST</SelectItem>
<SelectItem value="PUT">PUT</SelectItem>
<SelectItem value="DELETE">DELETE</SelectItem>
</SelectContent>
</Select>
)}
<p className="mt-1 text-xs text-muted-foreground">
{authTokenMode === "direct"
? "API 호출 시 Authorization 헤더에 사용할 토큰을 입력하세요."
: "auth_tokens 테이블에서 선택한 서비스의 최신 토큰을 사용합니다."}
</p>
</div>
</div>
{/* 엔드포인트 */}
<div>
<Label htmlFor="fromEndpoint"> *</Label>
<Input
id="fromEndpoint"
value={fromEndpoint}
onChange={(e) => setFromEndpoint(e.target.value)}
placeholder="/api/users"
/>
{/* 엔드포인트 + 데이터 배열 경로 — 한 행, 50:50 */}
<div className="grid grid-cols-2 gap-3">
<div>
<Label htmlFor="fromEndpoint" className="text-xs"> *</Label>
<Input
id="fromEndpoint"
value={fromEndpoint}
onChange={(e) => setFromEndpoint(e.target.value)}
placeholder="/api/users"
className="h-9 text-sm"
/>
</div>
<div>
<Label htmlFor="dataArrayPath" className="text-xs"> </Label>
<Input
id="dataArrayPath"
value={dataArrayPath}
onChange={(e) => setDataArrayPath(e.target.value)}
placeholder="resultData (비우면 자동 탐색)"
className="h-9 text-sm"
/>
</div>
</div>
{/* HTTP 메서드 */}
{/* 인증 토큰 — 라디오 + 입력을 한 행으로 압축 */}
<div>
<Label>HTTP </Label>
<Select value={fromApiMethod} onValueChange={(value: any) => setFromApiMethod(value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="GET">GET ( )</SelectItem>
<SelectItem value="POST">POST ( /)</SelectItem>
<SelectItem value="PUT">PUT</SelectItem>
<SelectItem value="DELETE">DELETE</SelectItem>
</SelectContent>
</Select>
</div>
{/* 데이터 배열 경로 */}
<div>
<Label htmlFor="dataArrayPath"> </Label>
<Input
id="dataArrayPath"
value={dataArrayPath}
onChange={(e) => setDataArrayPath(e.target.value)}
placeholder="response (예: data.items, results)"
/>
<p className="mt-1 text-xs text-muted-foreground">
API . .
<br />
예시: response, data.items, result.list
</p>
<Label className="text-xs"> </Label>
<div className="mt-1.5 flex items-center gap-2">
<div className="flex shrink-0 overflow-hidden rounded-md border">
<button
type="button"
onClick={() => { setAuthTokenMode("direct"); setAuthServiceName(""); }}
className={`px-2.5 py-1.5 text-xs ${authTokenMode === "direct" ? "bg-primary text-primary-foreground" : "bg-background hover:bg-muted"}`}
> </button>
<button
type="button"
onClick={() => setAuthTokenMode("db")}
className={`px-2.5 py-1.5 text-xs ${authTokenMode === "db" ? "bg-primary text-primary-foreground" : "bg-background hover:bg-muted"}`}
>DB에서 </button>
</div>
{authTokenMode === "direct" ? (
<Input
id="fromApiKey"
value={fromApiKey}
onChange={(e) => setFromApiKey(e.target.value)}
placeholder="Bearer eyJhbGciOiJIUzI1NiIs..."
className="h-9 flex-1 text-sm"
/>
) : (
<Select value={authServiceName || "none"} onValueChange={(value) => setAuthServiceName(value === "none" ? "" : value)}>
<SelectTrigger className="h-9 flex-1 text-sm">
<SelectValue placeholder="서비스명 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{authServiceNames.map((name) => (
<SelectItem key={name} value={name}>{name}</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
</div>
{/* Request Body (POST/PUT/DELETE용) */}