From e44b3cd6cc57f1dd8fb2bce2cad412bef28297e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Fri, 23 Jan 2026 10:20:12 +0900 Subject: [PATCH] =?UTF-8?q?refactor(WEB):=20/new=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=A5=BC=20=3Fmode=3Dnew=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 출퇴근 관리: 우림블루나인비즈니스센터 좌표 수정 (37.5572518, 126.864441) - 입금/출금/매출/카드 등록: /new 폴더 삭제 및 ?mode=new 쿼리 파라미터 방식으로 통합 - 매출 등록 페이지 제목 "등록 등록" 중복 수정 Co-Authored-By: Claude Opus 4.5 --- .../accounting/deposits/new/page.tsx | 7 -- .../(protected)/accounting/deposits/page.tsx | 18 ++++- .../(protected)/accounting/sales/new/page.tsx | 5 -- .../(protected)/accounting/sales/page.tsx | 18 ++++- .../accounting/withdrawals/new/page.tsx | 7 -- .../accounting/withdrawals/page.tsx | 18 ++++- .../(protected)/hr/attendance/page.tsx | 69 +++++++++++++++---- .../hr/card-management/new/page.tsx | 25 ------- .../(protected)/hr/card-management/page.tsx | 24 +++++++ .../SalesManagement/SalesDetail.tsx | 2 +- src/components/attendance/GoogleMap.tsx | 19 +++-- 11 files changed, 143 insertions(+), 69 deletions(-) delete mode 100644 src/app/[locale]/(protected)/accounting/deposits/new/page.tsx delete mode 100644 src/app/[locale]/(protected)/accounting/sales/new/page.tsx delete mode 100644 src/app/[locale]/(protected)/accounting/withdrawals/new/page.tsx delete mode 100644 src/app/[locale]/(protected)/hr/card-management/new/page.tsx diff --git a/src/app/[locale]/(protected)/accounting/deposits/new/page.tsx b/src/app/[locale]/(protected)/accounting/deposits/new/page.tsx deleted file mode 100644 index 2df329bd..00000000 --- a/src/app/[locale]/(protected)/accounting/deposits/new/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -import DepositDetailClientV2 from '@/components/accounting/DepositManagement/DepositDetailClientV2'; - -export default function DepositNewPage() { - return ; -} diff --git a/src/app/[locale]/(protected)/accounting/deposits/page.tsx b/src/app/[locale]/(protected)/accounting/deposits/page.tsx index 231333bd..47cc9998 100644 --- a/src/app/[locale]/(protected)/accounting/deposits/page.tsx +++ b/src/app/[locale]/(protected)/accounting/deposits/page.tsx @@ -1,7 +1,9 @@ 'use client'; import { useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; import { DepositManagement } from '@/components/accounting/DepositManagement'; +import DepositDetailClientV2 from '@/components/accounting/DepositManagement/DepositDetailClientV2'; import { getDeposits } from '@/components/accounting/DepositManagement/actions'; const DEFAULT_PAGINATION = { @@ -12,18 +14,27 @@ const DEFAULT_PAGINATION = { }; export default function DepositsPage() { + const searchParams = useSearchParams(); + const mode = searchParams.get('mode'); + const [data, setData] = useState>['data']>([]); const [pagination, setPagination] = useState(DEFAULT_PAGINATION); const [isLoading, setIsLoading] = useState(true); useEffect(() => { + // mode=new일 때는 데이터 로드 불필요 + if (mode === 'new') { + setIsLoading(false); + return; + } + getDeposits({ perPage: 100 }) .then(result => { setData(result.data); setPagination(result.pagination); }) .finally(() => setIsLoading(false)); - }, []); + }, [mode]); if (isLoading) { return ( @@ -33,6 +44,11 @@ export default function DepositsPage() { ); } + // mode=new일 때 등록 화면 표시 + if (mode === 'new') { + return ; + } + return ( ; -} \ No newline at end of file diff --git a/src/app/[locale]/(protected)/accounting/sales/page.tsx b/src/app/[locale]/(protected)/accounting/sales/page.tsx index fd3f8d32..3d313c0c 100644 --- a/src/app/[locale]/(protected)/accounting/sales/page.tsx +++ b/src/app/[locale]/(protected)/accounting/sales/page.tsx @@ -1,7 +1,9 @@ 'use client'; import { useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; import { SalesManagement } from '@/components/accounting/SalesManagement'; +import { SalesDetail } from '@/components/accounting/SalesManagement/SalesDetail'; import { getSales } from '@/components/accounting/SalesManagement/actions'; const DEFAULT_PAGINATION = { @@ -12,18 +14,27 @@ const DEFAULT_PAGINATION = { }; export default function SalesPage() { + const searchParams = useSearchParams(); + const mode = searchParams.get('mode'); + const [data, setData] = useState>['data']>([]); const [pagination, setPagination] = useState(DEFAULT_PAGINATION); const [isLoading, setIsLoading] = useState(true); useEffect(() => { + // mode=new일 때는 데이터 로드 불필요 + if (mode === 'new') { + setIsLoading(false); + return; + } + getSales({ perPage: 100 }) .then(result => { setData(result.data); setPagination(result.pagination); }) .finally(() => setIsLoading(false)); - }, []); + }, [mode]); if (isLoading) { return ( @@ -33,6 +44,11 @@ export default function SalesPage() { ); } + // mode=new일 때 등록 화면 표시 + if (mode === 'new') { + return ; + } + return ( ; -} diff --git a/src/app/[locale]/(protected)/accounting/withdrawals/page.tsx b/src/app/[locale]/(protected)/accounting/withdrawals/page.tsx index adb8f0e9..920e36af 100644 --- a/src/app/[locale]/(protected)/accounting/withdrawals/page.tsx +++ b/src/app/[locale]/(protected)/accounting/withdrawals/page.tsx @@ -1,7 +1,9 @@ 'use client'; import { useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; import { WithdrawalManagement } from '@/components/accounting/WithdrawalManagement'; +import WithdrawalDetailClientV2 from '@/components/accounting/WithdrawalManagement/WithdrawalDetailClientV2'; import { getWithdrawals } from '@/components/accounting/WithdrawalManagement/actions'; const DEFAULT_PAGINATION = { @@ -12,18 +14,27 @@ const DEFAULT_PAGINATION = { }; export default function WithdrawalsPage() { + const searchParams = useSearchParams(); + const mode = searchParams.get('mode'); + const [data, setData] = useState>['data']>([]); const [pagination, setPagination] = useState(DEFAULT_PAGINATION); const [isLoading, setIsLoading] = useState(true); useEffect(() => { + // mode=new일 때는 데이터 로드 불필요 + if (mode === 'new') { + setIsLoading(false); + return; + } + getWithdrawals({ perPage: 100 }) .then(result => { setData(result.data); setPagination(result.pagination); }) .finally(() => setIsLoading(false)); - }, []); + }, [mode]); if (isLoading) { return ( @@ -33,6 +44,11 @@ export default function WithdrawalsPage() { ); } + // mode=new일 때 등록 화면 표시 + if (mode === 'new') { + return ; + } + return ( (null); const [isMobile, setIsMobile] = useState(false); + // 출퇴근 설정 (API에서 로드) + const [siteLocation, setSiteLocation] = useState(DEFAULT_SITE_LOCATION); + const [isSettingsLoaded, setIsSettingsLoaded] = useState(false); + useEffect(() => { setMounted(true); }, []); + // 출퇴근 설정 로드 + useEffect(() => { + if (!mounted) return; + const loadSettings = async () => { + try { + const result = await getAttendanceSetting(); + console.log('[AttendancePage] API 응답:', result); + + if (result.success && result.data) { + const radius = result.data.allowedRadius; + console.log('[AttendancePage] API 반경값:', radius); + + // TODO: 주소/좌표 설정 UI 추가 후 아래 주석 해제 + // 현재는 테스트를 위해 좌표는 하드코딩, 반경만 API 연동 + const finalLocation = { + name: DEFAULT_SITE_LOCATION.name, // 하드코딩: 우림블루나인 + lat: DEFAULT_SITE_LOCATION.lat, // 하드코딩: 37.5494 + lng: DEFAULT_SITE_LOCATION.lng, // 하드코딩: 126.8747 + radius: (radius != null && Number(radius) > 0) ? Number(radius) : DEFAULT_SITE_LOCATION.radius, + }; + + console.log('[AttendancePage] 최종 위치:', finalLocation); + setSiteLocation(finalLocation); + } else { + console.log('[AttendancePage] API 실패, 기본값 사용'); + } + } catch (error) { + console.error('[AttendancePage] loadSettings error:', error); + } finally { + setIsSettingsLoaded(true); + } + }; + loadSettings(); + }, [mounted]); + useEffect(() => { const checkScreenSize = () => { setIsMobile(window.innerWidth < 768); @@ -169,13 +208,13 @@ export default function AttendancePage() { const handleConfirm = () => setViewMode('main'); const handleClose = () => router.back(); - if (!mounted) { + if (!mounted || !isSettingsLoaded) { return (
-

로딩 중...

+

출퇴근 설정을 불러오는 중...

@@ -185,7 +224,7 @@ export default function AttendancePage() { if (viewMode === 'check-in-complete') { return (
- +
); } @@ -193,7 +232,7 @@ export default function AttendancePage() { if (viewMode === 'check-out-complete') { return (
- +
); } @@ -206,7 +245,7 @@ export default function AttendancePage() { return (
- + {distance !== null && (
@@ -263,7 +302,7 @@ export default function AttendancePage() { {!isInRange && distance !== null && ( -

출퇴근 가능 범위({SITE_LOCATION.radius}m) 밖에 있습니다.

+

출퇴근 가능 범위({siteLocation.radius}m) 밖에 있습니다.

)}
@@ -274,7 +313,7 @@ export default function AttendancePage() { return (
- + {distance !== null && (
@@ -332,7 +371,7 @@ export default function AttendancePage() { {!isInRange && distance !== null && ( -

출퇴근 가능 범위({SITE_LOCATION.radius}m) 밖에 있습니다.

+

출퇴근 가능 범위({siteLocation.radius}m) 밖에 있습니다.

)} {attendanceStatus === 'checked-in' && ( diff --git a/src/app/[locale]/(protected)/hr/card-management/new/page.tsx b/src/app/[locale]/(protected)/hr/card-management/new/page.tsx deleted file mode 100644 index 0a1cbb4b..00000000 --- a/src/app/[locale]/(protected)/hr/card-management/new/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client'; - -/** - * 카드 등록 페이지 - IntegratedDetailTemplate 적용 - */ - -import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; -import { cardConfig } from '@/components/hr/CardManagement/cardConfig'; -import { createCard } from '@/components/hr/CardManagement/actions'; -import type { CardFormData } from '@/components/hr/CardManagement/types'; - -export default function NewCardPage() { - const handleSubmit = async (data: Record) => { - const result = await createCard(data as CardFormData); - return { success: result.success, error: result.error }; - }; - - return ( - - ); -} diff --git a/src/app/[locale]/(protected)/hr/card-management/page.tsx b/src/app/[locale]/(protected)/hr/card-management/page.tsx index 288bf67d..ccc5c92c 100644 --- a/src/app/[locale]/(protected)/hr/card-management/page.tsx +++ b/src/app/[locale]/(protected)/hr/card-management/page.tsx @@ -1,7 +1,31 @@ 'use client'; +import { useSearchParams } from 'next/navigation'; import { CardManagement } from '@/components/hr/CardManagement'; +import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; +import { cardConfig } from '@/components/hr/CardManagement/cardConfig'; +import { createCard } from '@/components/hr/CardManagement/actions'; +import type { CardFormData } from '@/components/hr/CardManagement/types'; export default function CardManagementPage() { + const searchParams = useSearchParams(); + const mode = searchParams.get('mode'); + + // mode=new일 때 등록 화면 표시 + if (mode === 'new') { + const handleSubmit = async (data: Record) => { + const result = await createCard(data as CardFormData); + return { success: result.success, error: result.error }; + }; + + return ( + + ); + } + return ; } diff --git a/src/components/accounting/SalesManagement/SalesDetail.tsx b/src/components/accounting/SalesManagement/SalesDetail.tsx index 35de1a54..9d127bec 100644 --- a/src/components/accounting/SalesManagement/SalesDetail.tsx +++ b/src/components/accounting/SalesManagement/SalesDetail.tsx @@ -557,7 +557,7 @@ export function SalesDetail({ mode, salesId }: SalesDetailProps) { // ===== 동적 config ===== const dynamicConfig = { ...salesConfig, - title: isNewMode ? '매출 상세_직접 등록' : '매출 상세', + title: isNewMode ? '매출' : '매출 상세', actions: { ...salesConfig.actions, submitLabel: isNewMode ? '등록' : '저장', diff --git a/src/components/attendance/GoogleMap.tsx b/src/components/attendance/GoogleMap.tsx index 64fc8149..d9670e91 100644 --- a/src/components/attendance/GoogleMap.tsx +++ b/src/components/attendance/GoogleMap.tsx @@ -131,10 +131,14 @@ export default function GoogleMap({ siteLocation, onDistanceChange }: GoogleMapP useEffect(() => { if (!isLoaded || !mapRef.current || !window.google) return; - console.log('[GoogleMap] 지도 초기화 시작'); + // 좌표 유효성 검사 - 우림블루나인비즈니스센터 기본값 (강서구 염창동) + const lat = typeof siteLocation.lat === 'number' && !isNaN(siteLocation.lat) ? siteLocation.lat : 37.5458; + const lng = typeof siteLocation.lng === 'number' && !isNaN(siteLocation.lng) ? siteLocation.lng : 126.8718; + + console.log('[GoogleMap] 지도 초기화 시작, 좌표:', lat, lng); const map = new window.google.maps.Map(mapRef.current, { - center: { lat: siteLocation.lat, lng: siteLocation.lng }, + center: { lat, lng }, zoom: 17, disableDefaultUI: true, zoomControl: true, @@ -145,11 +149,14 @@ export default function GoogleMap({ siteLocation, onDistanceChange }: GoogleMapP mapInstanceRef.current = map; - // 100m 반경 원 그리기 (파란색) + // radius 유효성 검사 + const radius = typeof siteLocation.radius === 'number' && siteLocation.radius > 0 ? siteLocation.radius : 100; + + // 반경 원 그리기 (파란색) const circle = new window.google.maps.Circle({ map: map, - center: { lat: siteLocation.lat, lng: siteLocation.lng }, - radius: siteLocation.radius, + center: { lat, lng }, + radius: radius, strokeColor: '#3B82F6', strokeOpacity: 0.8, strokeWeight: 2, @@ -162,7 +169,7 @@ export default function GoogleMap({ siteLocation, onDistanceChange }: GoogleMapP // 현장 중심 마커 (파란색) new window.google.maps.Marker({ map: map, - position: { lat: siteLocation.lat, lng: siteLocation.lng }, + position: { lat, lng }, icon: { path: window.google.maps.SymbolPath.CIRCLE, scale: 8,