Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/announcements/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function AnnouncementsContent() {
const { isAuthenticated } = useAuthStore();

// URL 쿼리 파라미터에서 페이지 번호 가져오기
const pageParam = searchParams.get('page');
const pageParam = searchParams?.get('page');
const page = pageParam ? parseInt(pageParam) : 0;

// 공지 사항 데이터 가져오기
Expand Down
18 changes: 13 additions & 5 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client";

import { useState, useEffect } from 'react';
import { useState, useEffect, Suspense } from 'react';
import { useTheme } from '@/contexts/ThemeContext';
import { UserCircleIcon, KeyIcon, ArrowLeftIcon } from '@heroicons/react/24/outline';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuthStore, LoginRequest } from '@/lib/authStore';
import Link from 'next/link';

export default function LoginPage() {
function LoginContent() {
const { currentTheme } = useTheme();
const router = useRouter();
const searchParams = useSearchParams();
Expand All @@ -28,8 +28,8 @@ export default function LoginPage() {
} = useAuthStore();

// silent 모드 확인 (내부 처리용)
const isSilentMode = searchParams.get('silent') === 'true';
const callbackUrl = searchParams.get('callbackUrl');
const isSilentMode = searchParams?.get('silent') === 'true';
const callbackUrl = searchParams?.get('callbackUrl');

// 페이지 로드 시 인증 상태 확인
useEffect(() => {
Expand Down Expand Up @@ -201,4 +201,12 @@ export default function LoginPage() {
</div>
</div>
);
}
}

export default function LoginPage() {
return (
<Suspense fallback={null}>
<LoginContent />
</Suspense>
);
}
50 changes: 29 additions & 21 deletions src/app/monitoring/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState, useRef } from 'react';
import { useEffect, useState, useRef, useCallback, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuthStore } from '@/lib/authStore';
import { useTheme } from '@/contexts/ThemeContext';
Expand Down Expand Up @@ -276,7 +276,7 @@ interface RouteGroup {
points: { lat: number; lng: number }[];
}

export default function MonitoringPage() {
function MonitoringContent() {
const router = useRouter();
const searchParams = useSearchParams();
const { isAuthenticated } = useAuthStore();
Expand Down Expand Up @@ -406,12 +406,28 @@ export default function MonitoringPage() {
};
}, []);

const handleSubscribe = useCallback(() => {
if (wsRef.current && companyId && wsConnected) {
console.log('구독 요청 전송:', companyId);

const subscribeMsg = JSON.stringify({
type: 'subscribe',
companyId: companyId
});

wsRef.current.send(subscribeMsg);
} else {
console.warn('WebSocket이 연결되지 않았거나 회사 ID가 없습니다');
setError('WebSocket이 연결되지 않았거나 회사 ID가 없습니다');
}
}, [companyId, wsConnected]);

useEffect(() => {
if (wsConnected && companyId) {
console.log('WebSocket 연결됨, 자동 구독 시도:', companyId);
handleSubscribe();
}
}, [wsConnected]);
}, [wsConnected, companyId, handleSubscribe]);

const connectWebSocket = () => {
try {
Expand Down Expand Up @@ -543,11 +559,11 @@ export default function MonitoringPage() {

useEffect(() => {
updateRoutePoints();
}, [currentPositions, showRoute]);
}, [currentPositions, showRoute, carLocations]);

useEffect(() => {
updateRoutePoints();
}, [showRoute]);
}, [showRoute, currentPositions, carLocations]);

const visibleMarkers = currentPositions
.filter(pos => showAllCars || selectedCars.includes(pos.carId))
Expand Down Expand Up @@ -582,22 +598,6 @@ export default function MonitoringPage() {
}
}, [currentPositions, selectedCars, mapSettings.followVehicle]);

const handleSubscribe = () => {
if (wsRef.current && companyId && wsConnected) {
console.log('구독 요청 전송:', companyId);

const subscribeMsg = JSON.stringify({
type: 'subscribe',
companyId: companyId
});

wsRef.current.send(subscribeMsg);
} else {
console.warn('WebSocket이 연결되지 않았거나 회사 ID가 없습니다');
setError('WebSocket이 연결되지 않았거나 회사 ID가 없습니다');
}
};

const handleRefresh = () => {
if (wsRef.current) {
wsRef.current.close();
Expand Down Expand Up @@ -771,4 +771,12 @@ export default function MonitoringPage() {
</div>
</div>
);
}

export default function MonitoringPage() {
return (
<Suspense fallback={null}>
<MonitoringContent />
</Suspense>
);
}
83 changes: 46 additions & 37 deletions src/app/permissions/[employeeId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default function EmployeePermissionsPage() {
const { currentTheme } = useTheme();
const params = useParams();
const router = useRouter();
const employeeId = params.employeeId as string;
const employeeId = params?.employeeId as string;
const { users, userPermissions, loadingPermissions, permissionsError, fetchUserPermissions,
updateUserPermissions, savingPermissions, savePermissionsError, savePermissionsSuccess, fetchUser } = useUserStore();
const [employee, setEmployee] = useState<Employee | null>(null);
Expand All @@ -65,8 +65,45 @@ export default function EmployeePermissionsPage() {
const [searchTerm, setSearchTerm] = useState('');
const [permissions, setPermissions] = useState<Permission[]>([]);

// 직원 정보 가져오기
// 권한을 그룹별로 정렬하는 함수
const getGroupedPermissions = useCallback(() => {
const groups = PERMISSION_GROUPS.map(group => ({
...group,
permissions: permissions.filter(p => {
if (group.id === 'admin') return p.id === 'PERM_ADMIN';
return p.id.startsWith(`PERM_${group.id.toUpperCase()}_`);
})
}));

return groups;
}, [permissions]);

// 검색어에 따라 권한 필터링
const filteredGroups = useMemo(() => {
const groupedPermissions = getGroupedPermissions();

if (!searchTerm) {
return groupedPermissions;
}

const filtered = groupedPermissions.map(group => ({
...group,
permissions: group.permissions.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.description.toLowerCase().includes(searchTerm.toLowerCase())
)
})).filter(group => group.permissions.length > 0);

return filtered;
}, [searchTerm, getGroupedPermissions]);

useEffect(() => {
if (!params?.employeeId) {
router.push('/employees');
return;
}

const employeeId = params.employeeId as string;
const fetchEmployeeData = async () => {
setLoadingEmployee(true);
setEmployeeError(null);
Expand Down Expand Up @@ -124,16 +161,20 @@ export default function EmployeePermissionsPage() {
};

fetchEmployeeData();
}, [employeeId, users, fetchUser]);
}, [params, router, users, fetchUser]);

// 권한 정보 가져오기
useEffect(() => {
if (!params?.employeeId) return;
const employeeId = params.employeeId as string;
if (employee) {
fetchUserPermissions(employeeId);
}
}, [employeeId, fetchUserPermissions, employee]);
}, [params, employee, fetchUserPermissions]);

useEffect(() => {
if (!params?.employeeId) return;
const employeeId = params.employeeId as string;
// 기본적으로 모든 권한을 isGranted가 false인 상태로 초기화
const allPermissionsCopy = ALL_PERMISSIONS.map(p => ({...p, isGranted: false}));

Expand Down Expand Up @@ -165,7 +206,7 @@ export default function EmployeePermissionsPage() {
}

setPermissions(allPermissionsCopy);
}, [employeeId, userPermissions, employee]);
}, [params, userPermissions, employee]);

// 권한 변경 처리
const handlePermissionChange = (permissionId: string, isGranted: boolean) => {
Expand All @@ -189,38 +230,6 @@ export default function EmployeePermissionsPage() {
await updateUserPermissions(employeeId, permissions);
};

// 권한을 그룹별로 정렬하는 함수
const getGroupedPermissions = useCallback(() => {
const groups = PERMISSION_GROUPS.map(group => ({
...group,
permissions: permissions.filter(p => {
if (group.id === 'admin') return p.id === 'PERM_ADMIN';
return p.id.startsWith(`PERM_${group.id.toUpperCase()}_`);
})
}));

return groups;
}, [permissions]);

// 검색어에 따라 권한 필터링
const filteredGroups = useMemo(() => {
const groupedPermissions = getGroupedPermissions();

if (!searchTerm) {
return groupedPermissions;
}

const filtered = groupedPermissions.map(group => ({
...group,
permissions: group.permissions.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.description.toLowerCase().includes(searchTerm.toLowerCase())
)
})).filter(group => group.permissions.length > 0);

return filtered;
}, [searchTerm, getGroupedPermissions]);

// 뒤로가기 함수
const handleGoBack = () => {
router.back();
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/CompanyDetailPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export interface Company {
interface CompanyDetailPanelProps {
isOpen: boolean;
onClose: () => void;
company: Company | null;
onUpdate?: (company: Company) => void;
company: Company;
onUpdate: (company: Company) => void;
}

export default function CompanyDetailPanel({
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/EmployeeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface Employee {
}

interface EmployeeListProps {
// 사용하지 않는 employees props 제거
// 필요한 props가 있다면 여기에 추가
}

export default function EmployeeList(props: EmployeeListProps) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/PageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function PageLayout({ children }: PageLayoutProps) {
const hideSidebarPaths = ['/', '/login', '/register'];

// 사이드바를 표시할지 여부 결정
const showSidebar = !hideSidebarPaths.includes(pathname);
const showSidebar = pathname ? !hideSidebarPaths.includes(pathname) : true;

// 인증 인터셉터 설정
useEffect(() => {
Expand Down
6 changes: 5 additions & 1 deletion src/components/logs/VehicleLogDetailSlidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ interface RoutePoint {
timestamp: string;
}

interface RouteResponse {
interface RouteData {
mdn: string;
route: RoutePoint[];
}

interface RouteResponse {
data: RouteData;
}

export default function VehicleLogDetailSlidePanel({ isOpen, onClose, log, onDelete, onUpdate }: VehicleLogDetailSlidePanelProps) {
const { currentTheme } = useTheme();
const [isEditing, setIsEditing] = useState(false);
Expand Down