Feature: Russian language support & Vehicle Requests improvements

- Add Russian language support (title_ru, subtitle_ru) for hero banners
- Add fuel/transmission translations for Mongolian (경유→Дизель, 오토→Автомат)
- Improve Vehicle Requests admin page:
  - Display real request ID and user email
  - Show detailed request info (maker, grade, year, fuel, mileage)
  - Replace modal search with Cars page integration
- Add "Add to Request" flow in Cars page for vehicle recommendations
- Fix image URL handling in FilmStripSlider and car detail page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
AutonetSellCar Deploy
2025-12-30 18:56:28 +09:00
parent 0ccc2f75c5
commit 1d8e4435b3
13 changed files with 306 additions and 271 deletions

View File

@@ -1,8 +1,9 @@
'use client';
import { useState, useEffect } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import Image from 'next/image';
import api, { heroBannersApi, carmodooApi } from '@/lib/api';
import api, { heroBannersApi, carmodooApi, vehicleRequestsApi } from '@/lib/api';
interface CarmodooMaker {
code: string;
@@ -193,6 +194,16 @@ export default function CarsAdminPage() {
}>;
} | null>(null);
// Vehicle Request mode (when coming from Vehicle Requests page)
const searchParams = useSearchParams();
const router = useRouter();
const requestId = searchParams.get('requestId');
const [addingToRequest, setAddingToRequest] = useState(false);
const [requestAddResult, setRequestAddResult] = useState<{
added: number;
errors: number;
} | null>(null);
// Dealer description editing state
const [showDescEditModal, setShowDescEditModal] = useState(false);
const [editingCar, setEditingCar] = useState<CarmodooCarItem | null>(null);
@@ -249,6 +260,33 @@ export default function CarsAdminPage() {
}
}, [filters.model_code]);
// Pre-fill filters from URL params (when coming from Vehicle Requests page)
useEffect(() => {
if (requestId) {
const makerCode = searchParams.get('maker_code') || '';
const modelCode = searchParams.get('model_code') || '';
const grade = searchParams.get('grade') || '';
const yearMin = searchParams.get('year_min') || '';
const yearMax = searchParams.get('year_max') || '';
const mileageMax = searchParams.get('mileage_max') || '';
const fuel = searchParams.get('fuel') || '';
setFilters(prev => ({
...prev,
maker_code: makerCode,
model_code: modelCode,
grade: grade,
year_min: yearMin,
year_max: yearMax,
mileage_max: mileageMax,
fuel: fuel,
}));
// Switch to Carmodoo tab
setActiveTab('carmodoo');
}
}, [requestId]);
const loadLocalCars = async (page = 1) => {
setLocalLoading(true);
try {
@@ -714,6 +752,76 @@ export default function CarsAdminPage() {
}
};
// 차량 추천 목록에 추가 함수 (Vehicle Request용)
const handleAddToRequest = async () => {
if (!requestId) return;
if (selectedCars.size === 0) {
alert('Please select at least one car to add to the request.');
return;
}
if (!confirm(`${selectedCars.size}개의 차량을 추천 목록에 추가하시겠습니까?`)) {
return;
}
setAddingToRequest(true);
setRequestAddResult(null);
try {
const selectedCarsList = cars.filter(car => selectedCars.has(car.id));
let addedCount = 0;
let errorCount = 0;
for (const car of selectedCarsList) {
try {
// Add vehicle to request
await vehicleRequestsApi.adminAddVehicle(parseInt(requestId), {
request_id: parseInt(requestId),
car_data: {
id: car.id,
car_name: car.car_name,
maker_name: car.maker_name,
model_name: car.model_name,
year: car.year,
mileage: car.mileage,
final_price: car.price,
fuel: car.fuel,
transmission: car.transmission,
color: car.color,
main_image: car.main_image,
},
is_approved: true,
});
addedCount++;
} catch (err) {
console.error(`Failed to add car ${car.id}:`, err);
errorCount++;
}
}
setRequestAddResult({
added: addedCount,
errors: errorCount,
});
setSelectedCars(new Set());
if (errorCount > 0) {
alert(`${addedCount}개의 차량이 추천 목록에 추가되었습니다.\n(실패: ${errorCount}개)`);
} else {
alert(`${addedCount}개의 차량이 추천 목록에 추가되었습니다.`);
}
// Redirect back to vehicle requests page
router.push('/admin/vehicle-requests');
} catch (err) {
console.error('Add to request failed:', err);
alert('추천 목록 추가에 실패했습니다.');
} finally {
setAddingToRequest(false);
}
};
const handleDeleteCar = async (carId: number) => {
if (!confirm('Are you sure you want to delete this car?')) {
return;
@@ -858,6 +966,29 @@ export default function CarsAdminPage() {
<div>
<h1 className="text-2xl font-bold text-gray-800 mb-6">Cars Management</h1>
{/* Request Mode Banner */}
{requestId && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-full p-2">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<p className="font-medium text-blue-800">Adding vehicles to Request #{requestId}</p>
<p className="text-sm text-blue-600">Search and select vehicles, then click "Add to Request" button.</p>
</div>
</div>
<button
onClick={() => router.push('/admin/vehicle-requests')}
className="text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1"
>
Back to Requests
</button>
</div>
)}
{/* Tabs */}
<div className="flex border-b border-gray-200 mb-6">
<button
@@ -1605,7 +1736,7 @@ export default function CarsAdminPage() {
</span>
<button
onClick={handleImport}
disabled={importing || registeringBanners || selectedCars.size === 0}
disabled={importing || registeringBanners || addingToRequest || selectedCars.size === 0}
className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50 flex items-center gap-2"
>
{importing ? (
@@ -1624,7 +1755,7 @@ export default function CarsAdminPage() {
</button>
<button
onClick={handleRegisterAsBanner}
disabled={importing || registeringBanners || selectedCars.size === 0}
disabled={importing || registeringBanners || addingToRequest || selectedCars.size === 0}
className="bg-purple-600 text-white px-4 py-2 rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50 flex items-center gap-2"
>
{registeringBanners ? (
@@ -1641,6 +1772,27 @@ export default function CarsAdminPage() {
</>
)}
</button>
{requestId && (
<button
onClick={handleAddToRequest}
disabled={importing || registeringBanners || addingToRequest || selectedCars.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"
>
{addingToRequest ? (
<>
<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>
</div>