feat: Add Complex (단지선택) filter to Carmodoo search

- Added complex_code parameter to search_cars method
- Added /api/carmodoo/complexes endpoint with list of Korean auto complexes
- Added Complex dropdown as first filter in admin Cars search
- Passes complex_code to Carmodoo API as c_danji parameter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
AutonetSellCar Deploy
2026-02-01 23:00:35 +09:00
parent a65c4bbfcf
commit 346018c4fb
2 changed files with 68 additions and 2 deletions

View File

@@ -170,6 +170,7 @@ class CarmodooClient:
price_max: Optional[int] = None,
fuel: Optional[str] = None,
transmission: Optional[str] = None,
complex_code: Optional[str] = None, # 단지 코드
page: int = 1,
page_size: int = 50
) -> List[dict]:
@@ -234,6 +235,8 @@ class CarmodooClient:
}
if transmission in trans_map:
form_data['dGA'] = trans_map[transmission]
if complex_code:
form_data['c_danji'] = complex_code # 단지 코드
ajax_headers = {
**self.headers,
@@ -1009,6 +1012,36 @@ async def get_grades(maker_code: str, model_code: str):
return grades
# 카모두 단지 목록 (자동차 매매단지)
CARMODOO_COMPLEXES = [
{"code": "", "name": "전체"},
{"code": "1", "name": "수원매매단지"},
{"code": "2", "name": "판교매매단지"},
{"code": "3", "name": "김포매매단지"},
{"code": "4", "name": "부산매매단지"},
{"code": "5", "name": "대구매매단지"},
{"code": "6", "name": "광주매매단지"},
{"code": "7", "name": "인천매매단지"},
{"code": "8", "name": "대전매매단지"},
{"code": "9", "name": "울산매매단지"},
{"code": "10", "name": "창원매매단지"},
{"code": "11", "name": "천안매매단지"},
{"code": "12", "name": "청주매매단지"},
{"code": "13", "name": "전주매매단지"},
]
class CarmodooComplex(BaseModel):
code: str
name: str
@router.get("/complexes")
def get_complexes():
"""단지 목록 (자동차 매매단지)"""
return [CarmodooComplex(**c) for c in CARMODOO_COMPLEXES]
class AdminSearchResultItem(BaseModel):
id: str
car_name: str
@@ -1048,6 +1081,7 @@ class AdminSearchResponse(BaseModel):
@router.get("/search", response_model=AdminSearchResponse)
async def admin_carmodoo_search(
complex_code: Optional[str] = None, # 단지 코드
maker_code: Optional[str] = None,
model_code: Optional[str] = None,
car_type: Optional[str] = None,
@@ -1084,6 +1118,7 @@ async def admin_carmodoo_search(
price_min=price_min,
price_max=price_max,
fuel=fuel,
complex_code=complex_code,
page=page,
page_size=page_size
)

View File

@@ -86,6 +86,7 @@ interface LocalCar {
}
interface SearchFilters {
complex_code: string; // 단지 코드
maker_code: string;
model_code: string;
grade: string;
@@ -99,6 +100,11 @@ interface SearchFilters {
displacement_max: string;
}
interface CarmodooComplex {
code: string;
name: string;
}
const FUEL_TYPES = [
{ value: '', label: 'All' },
{ value: '가솔린', label: 'Gasoline' },
@@ -166,6 +172,7 @@ export default function CarsAdminPage() {
const [totalCount, setTotalCount] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState<SearchFilters>({
complex_code: '',
maker_code: '',
model_code: '',
grade: '',
@@ -178,6 +185,7 @@ export default function CarsAdminPage() {
displacement_min: '',
displacement_max: '',
});
const [complexes, setComplexes] = useState<CarmodooComplex[]>([]);
const [importResult, setImportResult] = useState<{
imported: number;
skipped: number;
@@ -585,8 +593,12 @@ export default function CarsAdminPage() {
const loadInitialData = async () => {
try {
const makersRes = await api.get('/carmodoo/makers');
const [makersRes, complexesRes] = await Promise.all([
api.get('/carmodoo/makers'),
api.get('/carmodoo/complexes'),
]);
setMakers(makersRes.data);
setComplexes(complexesRes.data);
} catch (err) {
console.error('Failed to load initial data:', err);
}
@@ -623,6 +635,7 @@ export default function CarsAdminPage() {
try {
const params: Record<string, any> = { page, page_size: 20 };
if (filters.complex_code) params.complex_code = filters.complex_code;
if (filters.maker_code) params.maker_code = filters.maker_code;
if (filters.model_code) params.model_code = filters.model_code;
if (filters.grade) params.grade = filters.grade;
@@ -1953,8 +1966,26 @@ export default function CarsAdminPage() {
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 className="text-lg font-semibold text-gray-800 mb-4">Search from Carmodoo</h2>
{/* First Row: Maker, Model, Car Type, Grade */}
{/* First Row: Complex, Maker, Model, Grade */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
{/* Complex (단지선택) */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Complex ()
</label>
<select
value={filters.complex_code}
onChange={(e) => handleFilterChange('complex_code', e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
{complexes.map((complex) => (
<option key={complex.code} value={complex.code}>
{complex.name}
</option>
))}
</select>
</div>
{/* Maker (제조사) */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">