feat: Add Request functionality to All Cars tab

- Add checkbox selection for cars in All Cars tab when requestId is present
- Add "Add to Request" button with selection count
- Add select all/deselect all functionality
- Highlight selected cars with blue ring

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
AutonetSellCar Deploy
2026-02-01 21:13:55 +09:00
parent c9f03c1f86
commit 5881126408

View File

@@ -151,6 +151,8 @@ export default function CarsAdminPage() {
const [allCarsLoading, setAllCarsLoading] = useState(false);
const [allCarsTotal, setAllCarsTotal] = useState(0);
const [allCarsPage, setAllCarsPage] = useState(1);
const [selectedAllCars, setSelectedAllCars] = useState<Set<number>>(new Set());
const [addingAllCarsToRequest, setAddingAllCarsToRequest] = useState(false);
// Carmodoo search state
const [makers, setMakers] = useState<CarmodooMaker[]>([]);
@@ -1093,6 +1095,92 @@ export default function CarsAdminPage() {
}
};
// All Cars 탭에서 Request에 추가하는 함수
const handleAddAllCarsToRequest = async () => {
if (!requestId) return;
if (selectedAllCars.size === 0) {
alert('Please select at least one car to add to the request.');
return;
}
if (!confirm(`${selectedAllCars.size}개의 차량을 추천 목록에 추가하시겠습니까?`)) {
return;
}
setAddingAllCarsToRequest(true);
try {
const selectedCarsList = allCars.filter(car => selectedAllCars.has(car.id));
let addedCount = 0;
let errorCount = 0;
for (const car of selectedCarsList) {
try {
const mainImage = car.images?.find((img) => img.is_main)?.url || car.images?.[0]?.url;
await vehicleRequestsApi.adminAddVehicle(parseInt(requestId), {
request_id: parseInt(requestId),
car_id: car.id, // 이미 로컬 DB에 있는 차량이므로 car_id 사용
car_data: {
id: car.id.toString(),
car_name: car.car_name,
maker_name: car.maker?.name,
model_name: car.model?.name,
year: car.year,
mileage: car.mileage,
final_price: car.final_price_krw || car.price_krw,
fuel: car.fuel,
transmission: car.transmission,
color: car.color,
main_image: mainImage,
},
is_approved: true,
});
addedCount++;
} catch (err) {
console.error(`Failed to add car ${car.id}:`, err);
errorCount++;
}
}
setSelectedAllCars(new Set());
if (errorCount > 0) {
alert(`${addedCount}개의 차량이 추천 목록에 추가되었습니다.\n(실패: ${errorCount}개)`);
} else {
alert(`${addedCount}개의 차량이 추천 목록에 추가되었습니다.`);
}
router.push('/admin/vehicle-requests');
} catch (err) {
console.error('Add to request failed:', err);
alert('추천 목록 추가에 실패했습니다.');
} finally {
setAddingAllCarsToRequest(false);
}
};
// All Cars 선택 토글
const toggleAllCarSelection = (carId: number) => {
setSelectedAllCars(prev => {
const newSet = new Set(prev);
if (newSet.has(carId)) {
newSet.delete(carId);
} else {
newSet.add(carId);
}
return newSet;
});
};
// All Cars 전체 선택/해제
const handleSelectAllAllCars = () => {
if (selectedAllCars.size === allCars.length) {
setSelectedAllCars(new Set());
} else {
setSelectedAllCars(new Set(allCars.map(car => car.id)));
}
};
const handleDeleteCar = async (carId: number) => {
if (!confirm('Are you sure you want to delete this car?')) {
return;
@@ -1700,6 +1788,45 @@ export default function CarsAdminPage() {
This shows the cars that are visible to users (is_displayed = true). This is the same view as the public /cars page.
</p>
{/* Selection Controls for Request Mode */}
{requestId && allCars.length > 0 && (
<div className="flex items-center justify-between mb-4 p-3 bg-blue-50 rounded-lg">
<div className="flex items-center gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={selectedAllCars.size === allCars.length && allCars.length > 0}
onChange={handleSelectAllAllCars}
className="w-5 h-5 text-primary-600 rounded"
/>
<span className="text-sm font-medium text-gray-700">Select All</span>
</label>
<span className="text-sm text-blue-600 font-medium">
{selectedAllCars.size} selected
</span>
</div>
<button
onClick={handleAddAllCarsToRequest}
disabled={addingAllCarsToRequest || selectedAllCars.size === 0}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
>
{addingAllCarsToRequest ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
<span>Adding...</span>
</>
) : (
<>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span>Add to Request #{requestId}</span>
</>
)}
</button>
</div>
)}
{allCarsLoading ? (
<div className="flex justify-center py-12">
<div className="w-8 h-8 border-4 border-primary-600 border-t-transparent rounded-full animate-spin"></div>
@@ -1717,13 +1844,30 @@ export default function CarsAdminPage() {
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{allCars.map((car) => {
const mainImage = getImageUrl(car.images?.find((img) => img.is_main)?.url || car.images?.[0]?.url);
const isSelected = selectedAllCars.has(car.id);
return (
<div
key={car.id}
className="border rounded-lg overflow-hidden hover:shadow-md transition cursor-pointer"
className={`border rounded-lg overflow-hidden hover:shadow-md transition cursor-pointer ${
isSelected ? 'ring-2 ring-blue-500 border-blue-500' : ''
}`}
onClick={() => handleCarClick(car)}
>
<div className="relative h-40 bg-gray-100">
{/* Checkbox for Request Mode */}
{requestId && (
<div
className="absolute top-2 left-2 z-10"
onClick={(e) => e.stopPropagation()}
>
<input
type="checkbox"
checked={isSelected}
onChange={() => toggleAllCarSelection(car.id)}
className="w-5 h-5 text-blue-600 rounded cursor-pointer"
/>
</div>
)}
{mainImage ? (
<img
src={mainImage}