feat(WEB): API 인프라 리팩토링, CEO 대시보드 현황판 개선 및 문서 시스템 강화

- API: fetch-wrapper/proxy/refresh-token 리팩토링, authenticated-fetch 신규 추가
- CEO 대시보드: EnhancedSections 현황판 기능 개선, dashboard transformers/types 확장
- 문서 시스템: ApprovalLine/DocumentHeader/DocumentToolbar/DocumentViewer 개선
- 작업지시서: 검사보고서/작업일지 문서 컴포넌트 개선 (벤딩/스크린/슬랫)
- 레이아웃: Sidebar/AuthenticatedLayout 수정
- 작업자화면: WorkerScreen 수정

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-30 14:16:17 +09:00
parent a486977b80
commit 3ef9570f3b
27 changed files with 554 additions and 451 deletions

View File

@@ -191,6 +191,20 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
}),
}), [products, inadequateContent, overallResult]);
// PDF 호환 체크박스 렌더
const renderCheckbox = (checked: boolean, onClick: () => void) => (
<span
className={`inline-flex items-center justify-center w-3 h-3 border rounded-sm text-[8px] leading-none cursor-pointer select-none ${
checked ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-400 bg-white'
}`}
onClick={() => !readOnly && onClick()}
role="checkbox"
aria-checked={checked}
>
{checked ? '✓' : ''}
</span>
);
const inputClass = 'w-full text-center border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs';
// 전체 행 수 계산 (간격 포인트 수 합계)
@@ -202,7 +216,7 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
<div className="flex justify-between items-start mb-6">
<div>
<h1 className="text-2xl font-bold"> ()</h1>
<p className="text-xs text-gray-500 mt-1">
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
: {documentNo} | : {fullDate}
</p>
</div>
@@ -224,10 +238,10 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400"></td>
</tr>
<tr>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
</tr>
</tbody>
</table>
@@ -426,24 +440,12 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
{/* 절곡상태 - 양호/불량 체크 */}
<td className="border border-gray-400 p-1" rowSpan={rowCount}>
<div className="flex flex-col items-center gap-0.5">
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={product.bendingStatus === '양호'}
onChange={() => handleStatusChange(product.id, product.bendingStatus === '양호' ? null : '양호')}
disabled={readOnly}
className="w-3 h-3"
/>
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
{renderCheckbox(product.bendingStatus === '양호', () => handleStatusChange(product.id, product.bendingStatus === '양호' ? null : '양호'))}
</label>
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={product.bendingStatus === '불량'}
onChange={() => handleStatusChange(product.id, product.bendingStatus === '불량' ? null : '불량')}
disabled={readOnly}
className="w-3 h-3"
/>
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
{renderCheckbox(product.bendingStatus === '불량', () => handleStatusChange(product.id, product.bendingStatus === '불량' ? null : '불량'))}
</label>
</div>
@@ -484,16 +486,13 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
<table className="w-full border-collapse text-xs">
<tbody>
<tr>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-top"> </td>
<td className="border border-gray-400 px-3 py-2" colSpan={2}>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-middle text-center"> </td>
<td className="border border-gray-400 px-3 py-2">
<textarea value={inadequateContent} onChange={(e) => !readOnly && setInadequateContent(e.target.value)} disabled={readOnly}
className="w-full border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs resize-none" rows={2} />
</td>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium text-center w-24"></td>
</tr>
<tr>
<td className="border border-gray-400 px-3 py-2" colSpan={3}></td>
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm ${
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm w-24 ${
overallResult === '합격' ? 'text-blue-600' : overallResult === '불합격' ? 'text-red-600' : 'text-gray-400'
}`}>
{overallResult || '합격'}

View File

@@ -53,7 +53,7 @@ export function BendingWorkLogContent({ data: order }: BendingWorkLogContentProp
{/* 좌측: 제목 + 문서번호 */}
<div>
<h1 className="text-2xl font-bold"> ()</h1>
<p className="text-xs text-gray-500 mt-1">
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
: {documentNo} | : {fullDate}
</p>
</div>
@@ -75,10 +75,10 @@ export function BendingWorkLogContent({ data: order }: BendingWorkLogContentProp
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400"></td>
</tr>
<tr>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
</tr>
</tbody>
</table>

View File

@@ -161,7 +161,7 @@ export function InspectionReportModal({
) : (
<Save className="w-4 h-4 mr-1.5" />
)}
{isSaving ? '저장 중...' : '저장'}
</Button>
) : undefined;

View File

@@ -83,10 +83,19 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
));
}, [readOnly]);
// 숫자 콤마 포맷
const formatNumberWithComma = (value: string): string => {
const num = value.replace(/[^\d]/g, '');
if (!num) return '';
return Number(num).toLocaleString();
};
const handleInputChange = useCallback((rowId: number, field: 'lengthMeasured' | 'widthMeasured', value: string) => {
if (readOnly) return;
// 숫자만 저장 (콤마 제거)
const numOnly = value.replace(/[^\d]/g, '');
setRows(prev => prev.map(row =>
row.id === rowId ? { ...row, [field]: value } : row
row.id === rowId ? { ...row, [field]: numOnly } : row
));
}, [readOnly]);
@@ -135,28 +144,29 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
}),
}), [rows, inadequateContent, overallResult]);
// 체크박스 렌더 (양호/불량)
// PDF 호환 체크박스 렌더 (양호/불량)
const renderCheckbox = (checked: boolean, onClick: () => void) => (
<span
className={`inline-flex items-center justify-center w-3 h-3 border rounded-sm text-[8px] leading-none cursor-pointer select-none ${
checked ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-400 bg-white'
}`}
onClick={() => !readOnly && onClick()}
role="checkbox"
aria-checked={checked}
>
{checked ? '✓' : ''}
</span>
);
const renderCheckStatus = (rowId: number, field: 'processStatus' | 'sewingStatus' | 'assemblyStatus', value: CheckStatus) => (
<td className="border border-gray-400 p-1">
<div className="flex flex-col items-center gap-0.5">
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={value === '양호'}
onChange={() => handleStatusChange(rowId, field, value === '양호' ? null : '양호')}
disabled={readOnly}
className="w-3 h-3"
/>
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
{renderCheckbox(value === '양호', () => handleStatusChange(rowId, field, value === '양호' ? null : '양호'))}
</label>
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={value === '불량'}
onChange={() => handleStatusChange(rowId, field, value === '불량' ? null : '불량')}
disabled={readOnly}
className="w-3 h-3"
/>
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
{renderCheckbox(value === '불량', () => handleStatusChange(rowId, field, value === '불량' ? null : '불량'))}
</label>
</div>
@@ -171,7 +181,7 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
<div className="flex justify-between items-start mb-6">
<div>
<h1 className="text-2xl font-bold"> ()</h1>
<p className="text-xs text-gray-500 mt-1">
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
: {documentNo} | : {fullDate}
</p>
</div>
@@ -193,10 +203,10 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400"></td>
</tr>
<tr>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
</tr>
</tbody>
</table>
@@ -332,35 +342,23 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
{/* 길이 - 도면치수 표시 + 측정값 입력 */}
<td className="border border-gray-400 p-1 text-center">{row.lengthDesign}</td>
<td className="border border-gray-400 p-1">
<input type="text" value={row.lengthMeasured} onChange={(e) => handleInputChange(row.id, 'lengthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
<input type="text" value={formatNumberWithComma(row.lengthMeasured)} onChange={(e) => handleInputChange(row.id, 'lengthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
</td>
{/* 나비 - 도면치수 표시 + 측정값 입력 */}
<td className="border border-gray-400 p-1 text-center">{row.widthDesign}</td>
<td className="border border-gray-400 p-1">
<input type="text" value={row.widthMeasured} onChange={(e) => handleInputChange(row.id, 'widthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
<input type="text" value={formatNumberWithComma(row.widthMeasured)} onChange={(e) => handleInputChange(row.id, 'widthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
</td>
{/* 간격 - 기준치 표시 + OK/NG 선택 */}
<td className="border border-gray-400 p-1 text-center">{row.gapStandard}</td>
<td className="border border-gray-400 p-1">
<div className="flex flex-col items-center gap-0.5">
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={row.gapResult === 'OK'}
onChange={() => handleGapChange(row.id, row.gapResult === 'OK' ? null : 'OK')}
disabled={readOnly}
className="w-3 h-3"
/>
{renderCheckbox(row.gapResult === 'OK', () => handleGapChange(row.id, row.gapResult === 'OK' ? null : 'OK'))}
OK
</label>
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={row.gapResult === 'NG'}
onChange={() => handleGapChange(row.id, row.gapResult === 'NG' ? null : 'NG')}
disabled={readOnly}
className="w-3 h-3"
/>
{renderCheckbox(row.gapResult === 'NG', () => handleGapChange(row.id, row.gapResult === 'NG' ? null : 'NG'))}
NG
</label>
</div>
@@ -381,16 +379,13 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
<table className="w-full border-collapse text-xs">
<tbody>
<tr>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-top"> </td>
<td className="border border-gray-400 px-3 py-2" colSpan={2}>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-middle text-center"> </td>
<td className="border border-gray-400 px-3 py-2">
<textarea value={inadequateContent} onChange={(e) => !readOnly && setInadequateContent(e.target.value)} disabled={readOnly}
className="w-full border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs resize-none" rows={2} />
</td>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium text-center w-24"></td>
</tr>
<tr>
<td className="border border-gray-400 px-3 py-2" colSpan={3}></td>
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm ${
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm w-24 ${
overallResult === '합격' ? 'text-blue-600' : overallResult === '불합격' ? 'text-red-600' : 'text-gray-400'
}`}>
{overallResult || '합격'}

View File

@@ -55,7 +55,7 @@ export function ScreenWorkLogContent({ data: order }: ScreenWorkLogContentProps)
{/* 좌측: 제목 + 문서번호 */}
<div>
<h1 className="text-2xl font-bold"> ()</h1>
<p className="text-xs text-gray-500 mt-1">
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
: {documentNo} | : {fullDate}
</p>
</div>
@@ -77,10 +77,10 @@ export function ScreenWorkLogContent({ data: order }: ScreenWorkLogContentProps)
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400"></td>
</tr>
<tr>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
</tr>
</tbody>
</table>

View File

@@ -83,8 +83,10 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
const handleInputChange = useCallback((rowId: number, field: keyof InspectionRow, value: string) => {
if (readOnly) return;
// 숫자 + 소수점만 허용
const filtered = value.replace(/[^\d.]/g, '');
setRows(prev => prev.map(row =>
row.id === rowId ? { ...row, [field]: value } : row
row.id === rowId ? { ...row, [field]: filtered } : row
));
}, [readOnly]);
@@ -119,28 +121,29 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
}),
}), [rows, inadequateContent, overallResult]);
// 체크박스 렌더 (양호/불량)
// PDF 호환 체크박스 렌더 (양호/불량)
const renderCheckbox = (checked: boolean, onClick: () => void) => (
<span
className={`inline-flex items-center justify-center w-3 h-3 border rounded-sm text-[8px] leading-none cursor-pointer select-none ${
checked ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-400 bg-white'
}`}
onClick={() => !readOnly && onClick()}
role="checkbox"
aria-checked={checked}
>
{checked ? '✓' : ''}
</span>
);
const renderCheckStatus = (rowId: number, field: 'processStatus' | 'assemblyStatus', value: CheckStatus) => (
<td className="border border-gray-400 p-1">
<div className="flex flex-col items-center gap-0.5">
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={value === '양호'}
onChange={() => handleStatusChange(rowId, field, value === '양호' ? null : '양호')}
disabled={readOnly}
className="w-3 h-3"
/>
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
{renderCheckbox(value === '양호', () => handleStatusChange(rowId, field, value === '양호' ? null : '양호'))}
</label>
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
<input
type="checkbox"
checked={value === '불량'}
onChange={() => handleStatusChange(rowId, field, value === '불량' ? null : '불량')}
disabled={readOnly}
className="w-3 h-3"
/>
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
{renderCheckbox(value === '불량', () => handleStatusChange(rowId, field, value === '불량' ? null : '불량'))}
</label>
</div>
@@ -155,7 +158,7 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
<div className="flex justify-between items-start mb-6">
<div>
<h1 className="text-2xl font-bold"> ()</h1>
<p className="text-xs text-gray-500 mt-1">
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
: {documentNo} | : {fullDate}
</p>
</div>
@@ -177,10 +180,10 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400"></td>
</tr>
<tr>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
</tr>
</tbody>
</table>
@@ -339,16 +342,13 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
<table className="w-full border-collapse text-xs">
<tbody>
<tr>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-top"> </td>
<td className="border border-gray-400 px-3 py-2" colSpan={2}>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-middle text-center"> </td>
<td className="border border-gray-400 px-3 py-2">
<textarea value={inadequateContent} onChange={(e) => !readOnly && setInadequateContent(e.target.value)} disabled={readOnly}
className="w-full border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs resize-none" rows={2} />
</td>
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium text-center w-24"></td>
</tr>
<tr>
<td className="border border-gray-400 px-3 py-2" colSpan={3}></td>
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm ${
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm w-24 ${
overallResult === '합격' ? 'text-blue-600' : overallResult === '불합격' ? 'text-red-600' : 'text-gray-400'
}`}>
{overallResult || '합격'}

View File

@@ -51,7 +51,7 @@ export function SlatWorkLogContent({ data: order }: SlatWorkLogContentProps) {
{/* 좌측: 제목 + 문서번호 */}
<div>
<h1 className="text-2xl font-bold"> ()</h1>
<p className="text-xs text-gray-500 mt-1">
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
: {documentNo} | : {fullDate}
</p>
</div>
@@ -73,10 +73,10 @@ export function SlatWorkLogContent({ data: order }: SlatWorkLogContentProps) {
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400"></td>
</tr>
<tr>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap"></td>
</tr>
</tbody>
</table>

View File

@@ -14,6 +14,7 @@
*/
import { useState, useMemo, useCallback, useEffect } from 'react';
import { useMenuStore } from '@/store/menuStore';
import { ClipboardList, PlayCircle, CheckCircle2, AlertTriangle } from 'lucide-react';
import { ContentSkeleton } from '@/components/ui/skeleton';
import { Card, CardContent } from '@/components/ui/card';
@@ -188,6 +189,7 @@ const PROCESS_STEPS: Record<ProcessTab, { name: string; isMaterialInput: boolean
export default function WorkerScreen() {
// ===== 상태 관리 =====
const { sidebarCollapsed } = useMenuStore();
const [workOrders, setWorkOrders] = useState<WorkOrder[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [activeTab, setActiveTab] = useState<ProcessTab>('screen');
@@ -509,7 +511,7 @@ export default function WorkerScreen() {
return (
<PageLayout>
<div className="space-y-6">
<div className="space-y-6 pb-20">
{/* 완료 토스트 */}
{toastInfo && <CompletionToast info={toastInfo} />}
@@ -656,8 +658,8 @@ export default function WorkerScreen() {
</Tabs>
</div>
{/* 하단 고정 버튼 */}
<div className="sticky bottom-0 border-t border-gray-200 pt-4 pb-2 z-10">
{/* 하단 고정 버튼 - DetailActions 패턴 적용 */}
<div className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[48px] ${sidebarCollapsed ? 'md:left-[156px]' : 'md:left-[316px]'}`}>
<div className="flex gap-3">
<Button
variant="outline"