diff --git a/CLAUDE.md b/CLAUDE.md index acd18b8..d7d33f5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -660,6 +660,11 @@ Import 시 자동 번역 (Azure API) | 날짜 | 변경 내용 | |------|----------| +| 2024-12-31 | **Admin Settings 기능 추가**: Show Dealer Comment 토글, Korean Domestic + Export Customs 금액 설정 | +| 2024-12-31 | Cost 페이지에서 국내비용+수출통관비용 동적 적용 (settings API 연동) | +| 2024-12-31 | **Visitor Stats 국가별 통계 강화**: 국기 이모지 추가, 전용 Country Stats 카드 추가 | +| 2024-12-31 | Hero Banners API 라우트 순서 수정 (422 에러 해결) | +| 2024-12-31 | Banner Toggle 로직 수정 (HeroBanner 테이블 기준으로 변경) | | 2024-12-27 | **딜러 설명 번역 시스템 추가**: Azure Translator API 연동, 한국어→영어/몽골어/러시아어 직접 번역 | | 2024-12-27 | 관리자 번역 관리 페이지 추가 (`/admin/dealer-translations`) | | 2024-12-27 | DB 스키마 확장: `dealer_description_en/mn/ru` 컬럼 추가 | diff --git a/backend/app/api/hero_banners.py b/backend/app/api/hero_banners.py index a28fc52..4dbf8c7 100644 --- a/backend/app/api/hero_banners.py +++ b/backend/app/api/hero_banners.py @@ -143,6 +143,46 @@ def get_banner_cars( } +@router.put("/admin/reorder") +def reorder_banners( + request: BannerReorderRequest, + db: Session = Depends(get_db), + current_user: User = Depends(get_admin_user) +): + """배너 순서 재정렬 (Admin) + + car_ids: 배너 차량 ID 목록 (원하는 순서대로) + """ + for order, car_id in enumerate(request.car_ids): + banner = db.query(HeroBanner).filter(HeroBanner.car_id == car_id).first() + if banner: + banner.display_order = order + + db.commit() + return {"message": "Banner order updated", "count": len(request.car_ids)} + + +@router.put("/admin/settings", response_model=HeroBannerSettingsResponse) +def update_banner_settings( + settings_data: HeroBannerSettingsUpdate, + db: Session = Depends(get_db), + current_user: User = Depends(get_admin_user) +): + """배너 슬라이더 설정 수정 (Admin)""" + settings_obj = db.query(HeroBannerSettings).first() + if not settings_obj: + settings_obj = HeroBannerSettings() + db.add(settings_obj) + + update_data = settings_data.model_dump(exclude_unset=True) + for field, value in update_data.items(): + setattr(settings_obj, field, value) + + db.commit() + db.refresh(settings_obj) + return settings_obj + + @router.get("/admin/{banner_id}", response_model=HeroBannerResponse) def admin_get_banner( banner_id: int, @@ -218,27 +258,6 @@ def delete_banner( return {"message": "Banner deleted successfully"} -@router.put("/admin/settings", response_model=HeroBannerSettingsResponse) -def update_banner_settings( - settings_data: HeroBannerSettingsUpdate, - db: Session = Depends(get_db), - current_user: User = Depends(get_admin_user) -): - """배너 슬라이더 설정 수정 (Admin)""" - settings_obj = db.query(HeroBannerSettings).first() - if not settings_obj: - settings_obj = HeroBannerSettings() - db.add(settings_obj) - - update_data = settings_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(settings_obj, field, value) - - db.commit() - db.refresh(settings_obj) - return settings_obj - - # ==================== Image Upload ==================== @router.post("/admin/upload-image") @@ -297,18 +316,19 @@ def toggle_banner( ): """차량의 배너 상태 토글 (Admin) - - is_banner=False → True: HeroBanner 생성 - - is_banner=True → False: HeroBanner 삭제 + - HeroBanner 존재 → 삭제 + - HeroBanner 없음 → 생성 """ car = db.query(Car).filter(Car.id == car_id).first() if not car: raise HTTPException(status_code=404, detail="Car not found") - if car.is_banner: + # HeroBanner 테이블을 기준으로 판단 (car.is_banner 필드 대신) + existing_banner = db.query(HeroBanner).filter(HeroBanner.car_id == car_id).first() + + if existing_banner: # 배너에서 제거 - banner = db.query(HeroBanner).filter(HeroBanner.car_id == car_id).first() - if banner: - db.delete(banner) + db.delete(existing_banner) car.is_banner = False db.commit() return {"car_id": car_id, "is_banner": False, "message": "Removed from banner"} @@ -341,22 +361,3 @@ def toggle_banner( db.commit() db.refresh(banner) return {"car_id": car_id, "is_banner": True, "banner_id": banner.id, "message": "Added to banner"} - - -@router.put("/admin/reorder") -def reorder_banners( - request: BannerReorderRequest, - db: Session = Depends(get_db), - current_user: User = Depends(get_admin_user) -): - """배너 순서 재정렬 (Admin) - - car_ids: 배너 차량 ID 목록 (원하는 순서대로) - """ - for order, car_id in enumerate(request.car_ids): - banner = db.query(HeroBanner).filter(HeroBanner.car_id == car_id).first() - if banner: - banner.display_order = order - - db.commit() - return {"message": "Banner order updated", "count": len(request.car_ids)} diff --git a/backend/app/models/settings.py b/backend/app/models/settings.py index f39681d..4d6186a 100644 --- a/backend/app/models/settings.py +++ b/backend/app/models/settings.py @@ -29,6 +29,12 @@ class SystemSettings(Base): container_logistics_usd = Column(Integer, default=3600) # 컨테이너 물류비 $3,600 shoring_cost_usd = Column(Integer, default=300) # 쇼링비 (컨테이너 고정) $300 + # 국내비용 + 수출통관비용 (KRW) + domestic_export_customs_krw = Column(Integer, default=1150000) # ₩1,150,000 + + # 딜러 코멘트 표시 설정 + show_dealer_comment = Column(Boolean, default=True) # 딜러 코멘트 표시 여부 + # 레퍼럴 보상 설정 referral_reward_enabled = Column(Boolean, default=True) # 레퍼럴 보상 활성화 referral_reward_percent = Column(Float, default=10.0) # 보상 비율 (기본 10%) diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py index d08a75b..93d3267 100644 --- a/backend/app/schemas/settings.py +++ b/backend/app/schemas/settings.py @@ -15,6 +15,11 @@ class SystemSettingsUpdate(BaseModel): cache_ttl_hours: Optional[int] = None container_logistics_usd: Optional[int] = None shoring_cost_usd: Optional[int] = None + domestic_export_customs_krw: Optional[int] = None + show_dealer_comment: Optional[bool] = None + referral_reward_enabled: Optional[bool] = None + referral_reward_percent: Optional[float] = None + referral_reward_type: Optional[str] = None class SystemSettingsResponse(BaseModel): @@ -30,6 +35,11 @@ class SystemSettingsResponse(BaseModel): cache_ttl_hours: int container_logistics_usd: int shoring_cost_usd: int + domestic_export_customs_krw: int = 1150000 + show_dealer_comment: bool = True + referral_reward_enabled: bool = True + referral_reward_percent: float = 10.0 + referral_reward_type: str = "one_time" created_at: Optional[datetime] = None updated_at: Optional[datetime] = None diff --git a/frontend/src/app/admin/settings/page.tsx b/frontend/src/app/admin/settings/page.tsx index a7cde09..98085ed 100644 --- a/frontend/src/app/admin/settings/page.tsx +++ b/frontend/src/app/admin/settings/page.tsx @@ -14,6 +14,8 @@ interface SystemSettings { cache_ttl_hours: number; container_logistics_usd: number; shoring_cost_usd: number; + domestic_export_customs_krw: number; + show_dealer_comment: boolean; referral_reward_enabled: boolean; referral_reward_percent: number; referral_reward_type: string; @@ -57,6 +59,8 @@ export default function SettingsPage() { cache_ttl_hours: 2, container_logistics_usd: 3600, shoring_cost_usd: 300, + domestic_export_customs_krw: 1150000, + show_dealer_comment: true, referral_reward_enabled: true, referral_reward_percent: 10.0, referral_reward_type: 'one_time', @@ -84,6 +88,8 @@ export default function SettingsPage() { cache_ttl_hours: data.cache_ttl_hours, container_logistics_usd: data.container_logistics_usd || 3600, shoring_cost_usd: data.shoring_cost_usd || 300, + domestic_export_customs_krw: data.domestic_export_customs_krw || 1150000, + show_dealer_comment: data.show_dealer_comment ?? true, referral_reward_enabled: data.referral_reward_enabled ?? true, referral_reward_percent: data.referral_reward_percent ?? 10.0, referral_reward_type: data.referral_reward_type || 'one_time', @@ -234,6 +240,31 @@ export default function SettingsPage() { + {/* Display Settings */} +
차량 상세 페이지에서 딜러 코멘트 표시 여부
+쇼링비 - 컨테이너 고정 비용 (기본값: $300)
국내비용 + 수출통관비용 (기본값: ₩1,150,000)
+Total Container Cost: ${formData.container_logistics_usd + formData.shoring_cost_usd}
Small Car (5.5/10): ${((formData.container_logistics_usd + formData.shoring_cost_usd) * 0.275).toFixed(0)} per car
Compact Car (4.5/10): ${((formData.container_logistics_usd + formData.shoring_cost_usd) * 0.225).toFixed(0)} per car
+Korean Domestic + Export Customs: ₩{formData.domestic_export_customs_krw.toLocaleString()}