Skip to content

refactor: Home feature 컴포넌트 도메인별 분리#40

Merged
soyeoung1 merged 11 commits into
developfrom
refactor/home-feature-split
Jan 17, 2026
Merged

refactor: Home feature 컴포넌트 도메인별 분리#40
soyeoung1 merged 11 commits into
developfrom
refactor/home-feature-split

Conversation

@soyeoung1

Copy link
Copy Markdown
Contributor

🔗 관련 이슈

Resolves: #35

📝 작업 내용

Home 컴포넌트 도메인별 feature로 분리

📷 스크린샷 (UI 작업인 경우)

x

💬 리뷰어에게

✅ 기본 체크리스트

  • 코드가 정상적으로 실행되나요? (yarn start 빌드 에러 없음)
  • (중요) 안드로이드/iOS 시뮬레이터에서 각각 확인했나요?
  • 불필요한 console.log나 주석을 제거했나요?
  • yarn format을 실행하여 코드 스타일을 정렬했나요?
  • yarn lint를 실행하여 린트 에러가 없나요?

🏗️ 구조 체크리스트 (Self-Check)

💡 아래 항목을 위반하면 코드 리뷰어에게 반려될 수 있습니다!

파일 및 폴더

  • 1개 기능 전용 컴포넌트features/ 안에 두었나요? (❌ shared/ui 금지)
  • 재사용 컴포넌트shared/ui에 두었나요?
  • 파일이 3개 이상일 때만 폴더로 묶었나요? (불필요한 index.ts 방지)

상태 관리 & 네이밍

  • 서버 데이터는 React Query, UI 상태는 Zustand를 사용했나요?
  • Boolean 변수is, has, should로 시작하나요?

🚀 모든 체크리스트를 확인한 후 PR을 생성하세요!

@soyeoung1 soyeoung1 self-assigned this Jan 1, 2026
Copilot AI review requested due to automatic review settings January 1, 2026 12:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

이 PR은 Home 화면의 컴포넌트들을 도메인별로 분리하여 기능별 폴더 구조를 개선하는 리팩토링 작업입니다.

  • 기존 Home feature의 컴포넌트들을 weather, bus, match 도메인으로 분리
  • 컴포넌트 이름을 더 명확하게 변경 (WeatherInform → WeatherWidget 등)
  • weather feature에 템플릿 구조 추가 (types, store, model, hooks, api, components 폴더)

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/features/home/screens/HomeScreen.tsx 분리된 도메인 컴포넌트를 import하도록 경로 및 이름 업데이트
src/features/weather/components/WeatherWidget.tsx WeatherInform에서 WeatherWidget으로 컴포넌트명 변경
src/features/match/components/MatchingStatusCard.tsx MatchingStatus에서 MatchingStatusCard로 컴포넌트명 변경
src/features/bus/components/BusArrivalSummary.tsx BusArrivalDetailBox에서 BusArrivalSummary로 컴포넌트명 변경
src/features/weather/types/example.ts weather feature에 Example 타입 정의 추가 (템플릿)
src/features/weather/store/useExampleStore.ts weather feature에 Example 스토어 추가 (템플릿)
src/features/weather/model/exampleUtils.ts weather feature에 예제 비즈니스 로직 추가 (템플릿)
src/features/weather/hooks/useExampleQuery.ts weather feature에 Query 훅 추가 (템플릿)
src/features/weather/hooks/useExampleMutation.ts weather feature에 Mutation 훅 추가 (템플릿)
src/features/weather/components/ExampleCard.tsx weather feature에 ExampleCard 컴포넌트 추가 (템플릿)
src/features/weather/components/ExampleList.tsx weather feature에 ExampleList 컴포넌트 추가 (템플릿)
src/features/weather/api/getExamples.ts weather feature에 조회 API 함수 추가 (템플릿)
src/features/weather/api/mutateExample.ts weather feature에 생성/삭제 API 함수 추가 (템플릿)
src/features/weather/{types,store,model,hooks,api,components}/README.md 각 폴더별 가이드 문서 추가

Comment on lines +1 to +49
// React Query: 데이터 조회 (Query)
import { useQuery } from '@tanstack/react-query';

import { getExampleById, getExampleList } from '../api/getExamples';

/**
* 예제 목록을 가져오는 React Query 훅
*
* @example
* ```tsx
* const { data, isLoading, error } = useExampleListQuery();
*
* if (isLoading) return <Text>로딩 중...</Text>;
* if (error) return <Text>에러 발생</Text>;
*
* return <ExampleList data={data} />;
* ```
*/
export function useExampleListQuery() {
return useQuery({
queryKey: ['examples'],
queryFn: getExampleList,
// 5분 동안 데이터를 신선하게 유지 (재요청 안 함)
staleTime: 1000 * 60 * 5,
});
}

/**
* 특정 ID의 예제를 가져오는 React Query 훅
*
* @param id 조회할 예제 ID
*
* @example
* ```tsx
* const { data: example, isLoading } = useExampleQuery('example-123');
*
* if (isLoading) return <Text>로딩 중...</Text>;
*
* return <ExampleDetail data={example} />;
* ```
*/
export function useExampleQuery(id: string) {
return useQuery({
queryKey: ['examples', id],
queryFn: () => getExampleById(id),
// ID가 없으면 비활성화
enabled: !!id,
});
}

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 예제(Example) 데이터를 조회하는 React Query 훅이 있습니다. 이 훅들은 weather 도메인과 무관합니다. weather 폴더에는 날씨 데이터를 조회하는 훅(예: useWeatherQuery)만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +1 to +77
// React Query: 데이터 생성/수정/삭제 (Mutation)
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { createExample, deleteExample } from '../api/mutateExample';
import { CreateExampleRequest } from '../types/example';

/**
* 새 예제를 생성하는 Mutation 훅
* 성공 시 자동으로 목록을 새로고침합니다.
*
* @example
* ```tsx
* const createMutation = useCreateExampleMutation();
*
* const handleCreate = () => {
* createMutation.mutate({
* title: '금정역 → 한세대',
* description: '오후 3시 출발',
* location: 'kumjeong',
* });
* };
*
* return (
* <Button
* onPress={handleCreate}
* disabled={createMutation.isPending}
* >
* 생성하기
* </Button>
* );
* ```
*/
export function useCreateExampleMutation() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (data: CreateExampleRequest) => createExample(data),
onSuccess: () => {
// 성공 시 목록 다시 불러오기
queryClient.invalidateQueries({ queryKey: ['examples'] });
},
});
}

/**
* 예제를 삭제하는 Mutation 훅
* 성공 시 자동으로 목록을 새로고침합니다.
*
* @example
* ```tsx
* const deleteMutation = useDeleteExampleMutation();
*
* const handleDelete = (id: string) => {
* deleteMutation.mutate(id);
* };
*
* return (
* <Button
* onPress={() => handleDelete('example-123')}
* disabled={deleteMutation.isPending}
* >
* 삭제하기
* </Button>
* );
* ```
*/
export function useDeleteExampleMutation() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (id: string) => deleteExample(id),
onSuccess: () => {
// 성공 시 목록 다시 불러오기
queryClient.invalidateQueries({ queryKey: ['examples'] });
},
});
}

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 예제(Example) 데이터를 생성/삭제하는 Mutation 훅이 있습니다. 이 훅들은 weather 도메인과 무관합니다. weather 폴더에는 날씨 데이터 관련 Mutation만 있어야 합니다(또는 날씨 데이터는 보통 읽기 전용이므로 Mutation이 필요 없을 수도 있습니다). 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment thread src/features/weather/api/getExamples.ts Outdated
Comment on lines +1 to +23
// 예제: 택시 매칭 목록 조회
import axios from 'axios';

import { Example } from '../types/example';

/**
* 서버에서 예제 목록을 가져옵니다.
* @returns 예제 목록 배열
*/
export async function getExampleList(): Promise<Example[]> {
const response = await axios.get('/api/examples');
return response.data;
}

/**
* 특정 ID의 예제를 가져옵니다.
* @param id 예제 ID
* @returns 예제 객체
*/
export async function getExampleById(id: string): Promise<Example> {
const response = await axios.get(`/api/examples/${id}`);
return response.data;
}

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 예제(Example) 데이터를 조회하는 API 함수가 있습니다. 주석에는 "택시 매칭 목록 조회"라고 명시되어 있어 weather 도메인과 완전히 무관합니다. weather 폴더에는 날씨 API 호출 함수만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +1 to +100
// 예제: 리스트 컴포넌트 (FlatList 사용)
import styled from '@emotion/native';
import { FlatList } from 'react-native';

import { Example } from '../types/example';

import { ExampleCard } from './ExampleCard';

type ExampleListProps = {
/** 표시할 예제 목록 */
data: Example[];

/** 카드 클릭 시 실행할 함수 */
onItemPress: (id: string) => void;

/** 현재 선택된 예제 ID */
selectedId?: string | null;

/** 로딩 상태 */
isLoading?: boolean;

/** 빈 목록 메시지 */
emptyMessage?: string;
};

/**
* 예제 목록을 FlatList로 표시하는 컴포넌트
*
* @example
* ```tsx
* const { data, isLoading } = useExampleListQuery();
* const { selectedExampleId, setSelectedExampleId } = useExampleStore();
*
* <ExampleList
* data={data ?? []}
* onItemPress={setSelectedExampleId}
* selectedId={selectedExampleId}
* isLoading={isLoading}
* />
* ```
*/
export function ExampleList({
data,
onItemPress,
selectedId,
isLoading = false,
emptyMessage = '표시할 항목이 없습니다.',
}: ExampleListProps) {
if (isLoading) {
return (
<CenterContainer>
<LoadingText>로딩 중...</LoadingText>
</CenterContainer>
);
}

if (data.length === 0) {
return (
<CenterContainer>
<EmptyText>{emptyMessage}</EmptyText>
</CenterContainer>
);
}

return (
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<ExampleCard
data={item}
onPress={() => onItemPress(item.id)}
isSelected={item.id === selectedId}
/>
)}
contentContainerStyle={{ padding: 16 }}
showsVerticalScrollIndicator={false}
/>
);
}

// --- 스타일 정의 ---

const CenterContainer = styled.View`
flex: 1;
justify-content: center;
align-items: center;
padding: 32px;
`;

const LoadingText = styled.Text`
font-size: 16px;
color: #757575;
`;

const EmptyText = styled.Text`
font-size: 16px;
color: #9e9e9e;
text-align: center;
`;

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 ExampleList 컴포넌트가 있습니다. 이 컴포넌트는 예제 목록을 표시하는 것으로 weather 도메인과 무관합니다. weather 폴더에는 날씨 관련 컴포넌트만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment thread src/features/weather/types/example.ts Outdated
Comment on lines +1 to +41
// 예제 타입 정의

/**
* 서버에서 받는 예제 객체 타입
*/
export type Example = {
id: string;
title: string;
description: string;
location: 'kumjeong' | 'dangjeong' | 'hansei';
status: 'active' | 'completed' | 'cancelled';
createdAt: string;
updatedAt: string;
};

/**
* 예제 생성 요청 타입
*/
export type CreateExampleRequest = {
title: string;
description: string;
location: 'kumjeong' | 'dangjeong' | 'hansei';
};

/**
* 예제 수정 요청 타입
*/
export type UpdateExampleRequest = {
title?: string;
description?: string;
status?: 'active' | 'completed' | 'cancelled';
};

/**
* 예제 필터 타입
*/
export type ExampleFilter = {
location: 'all' | 'kumjeong' | 'dangjeong' | 'hansei';
status: 'all' | 'active' | 'completed' | 'cancelled';
searchQuery: string;
};

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더 안에 예제(Example) 타입이 정의되어 있습니다. 이 파일은 택시 매칭이나 예제 도메인에 해당하는 타입들을 포함하고 있어 weather 도메인과 관련이 없습니다. weather 폴더에는 날씨 관련 타입(예: WeatherData, Temperature, WeatherCondition 등)만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +1 to +22
// 예제: 새 항목 생성
import axios from 'axios';

import { CreateExampleRequest, Example } from '../types/example';

/**
* 새로운 예제를 생성합니다.
* @param data 생성할 예제 데이터
* @returns 생성된 예제 객체
*/
export async function createExample(data: CreateExampleRequest): Promise<Example> {
const response = await axios.post('/api/examples', data);
return response.data;
}

/**
* 예제를 삭제합니다.
* @param id 삭제할 예제 ID
*/
export async function deleteExample(id: string): Promise<void> {
await axios.delete(`/api/examples/${id}`);
}

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 예제(Example) 데이터를 생성/삭제하는 API 함수가 있습니다. 이 함수들은 weather 도메인과 무관합니다. weather 폴더에는 날씨 API 호출 함수만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +1 to +139
// 예제: 카드 형태의 UI 컴포넌트
import styled from '@emotion/native';

import { Example } from '../types/example';

type ExampleCardProps = {
/** 표시할 예제 데이터 */
data: Example;

/** 카드 클릭 시 실행할 함수 */
onPress: () => void;

/** 선택된 상태 여부 (선택 시 하이라이트 표시) */
isSelected?: boolean;
};

/**
* 예제 정보를 카드 형태로 표시하는 컴포넌트
*
* @example
* ```tsx
* <ExampleCard
* data={example}
* onPress={() => handlePress(example.id)}
* isSelected={selectedId === example.id}
* />
* ```
*/
export function ExampleCard({ data, onPress, isSelected = false }: ExampleCardProps) {
return (
<Card onPress={onPress} isSelected={isSelected}>
<Header>
<Title>{data.title}</Title>
<StatusBadge status={data.status}>
<StatusText>{getStatusLabel(data.status)}</StatusText>
</StatusBadge>
</Header>

<Description numberOfLines={2}>{data.description}</Description>

<Footer>
<LocationText>📍 {getLocationLabel(data.location)}</LocationText>
</Footer>
</Card>
);
}

// --- 스타일 정의 ---

const Card = styled.Pressable<{ isSelected: boolean }>`
padding: 16px;
background-color: ${(props) => (props.isSelected ? '#e3f2fd' : '#fff')};
border-radius: 12px;
border-width: 1px;
border-color: ${(props) => (props.isSelected ? '#2196f3' : '#e0e0e0')};
margin-bottom: 12px;
`;

const Header = styled.View`
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
`;

const Title = styled.Text`
font-size: 18px;
font-weight: bold;
color: #212121;
flex: 1;
`;

const StatusBadge = styled.View<{ status: Example['status'] }>`
padding: 4px 12px;
border-radius: 12px;
background-color: ${(props) => {
switch (props.status) {
case 'active':
return '#4caf50';
case 'completed':
return '#9e9e9e';
case 'cancelled':
return '#f44336';
default:
return '#9e9e9e';
}
}};
`;

const StatusText = styled.Text`
font-size: 12px;
color: #fff;
font-weight: 600;
`;

const Description = styled.Text`
font-size: 14px;
color: #666;
margin-bottom: 12px;
line-height: 20px;
`;

const Footer = styled.View`
flex-direction: row;
align-items: center;
`;

const LocationText = styled.Text`
font-size: 14px;
color: #757575;
`;

// --- 유틸리티 함수 ---

function getStatusLabel(status: Example['status']): string {
switch (status) {
case 'active':
return '모집중';
case 'completed':
return '완료';
case 'cancelled':
return '취소됨';
default:
return '알 수 없음';
}
}

function getLocationLabel(location: Example['location']): string {
switch (location) {
case 'kumjeong':
return '금정역';
case 'dangjeong':
return '당정역';
case 'hansei':
return '한세대';
default:
return '알 수 없음';
}
}

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 ExampleCard 컴포넌트가 있습니다. 이 컴포넌트는 택시 매칭이나 다른 도메인의 카드로 보이며, weather 도메인과 무관합니다. weather 폴더에는 WeatherWidget 같은 날씨 관련 컴포넌트만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment thread src/features/weather/model/README.md Outdated
Comment on lines +1 to +36
# 상태 관리

이 폴더에는 Zustand를 사용한 상태 관리 코드를 작성합니다.

## 예시

```tsx
// useExampleStore.ts
import { create } from 'zustand';

interface ExampleState {
selectedId: string | null;
searchQuery: string;
setSelectedId: (id: string | null) => void;
setSearchQuery: (query: string) => void;
}

export const useExampleStore = create<ExampleState>((set) => ({
selectedId: null,
searchQuery: '',
setSelectedId: (id) => set({ selectedId: id }),
setSearchQuery: (query) => set({ searchQuery: query }),
}));
```

## 사용 방법

```tsx
import { useExampleStore } from '@features/example/model/useExampleStore';

function MyComponent() {
const { selectedId, setSelectedId } = useExampleStore();

return <Button onPress={() => setSelectedId('123')}>선택하기</Button>;
}
```

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

model 폴더의 README.md 파일 내용이 잘못되었습니다. 파일 제목과 내용이 "상태 관리" 및 "Zustand"에 관한 것인데, model 폴더는 비즈니스 로직을 위한 폴더입니다. Zustand 상태 관리는 store 폴더에 있어야 합니다. README 내용을 model 폴더의 목적에 맞게 수정해야 합니다 (예: 비즈니스 로직, 유틸리티 함수, 계산 로직 등).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +97
// Zustand: 클라이언트 상태 관리
import { create } from 'zustand';

import { ExampleFilter } from '../types/example';

/**
* 예제 기능의 클라이언트 상태를 관리하는 Zustand 스토어
*
* 🚨 주의: 서버 데이터는 여기에 저장하지 마세요!
* - ✅ OK: 선택된 항목, 검색어, 필터 설정, UI 상태
* - ❌ NO: 서버에서 가져온 목록, 유저 정보 (React Query 사용!)
*/
type ExampleState = {
// --- 상태 ---
/** 현재 선택된 예제 ID */
selectedExampleId: string | null;

/** 검색어 */
searchQuery: string;

/** 필터 설정 */
filter: ExampleFilter;

/** 모달 열림/닫힘 상태 */
isCreateModalOpen: boolean;

// --- 액션 ---
/** 예제 선택 */
setSelectedExampleId: (id: string | null) => void;

/** 검색어 변경 */
setSearchQuery: (query: string) => void;

/** 필터 변경 */
setFilter: (filter: Partial<ExampleFilter>) => void;

/** 모달 열기/닫기 */
openCreateModal: () => void;
closeCreateModal: () => void;

/** 모든 상태 초기화 */
reset: () => void;
};

const initialFilter: ExampleFilter = {
location: 'all',
status: 'all',
searchQuery: '',
};

export const useExampleStore = create<ExampleState>((set) => ({
// 초기 상태
selectedExampleId: null,
searchQuery: '',
filter: initialFilter,
isCreateModalOpen: false,

// 액션 구현
setSelectedExampleId: (id) => set({ selectedExampleId: id }),

setSearchQuery: (query) => set({ searchQuery: query }),

setFilter: (newFilter) =>
set((state) => ({
filter: { ...state.filter, ...newFilter },
})),

openCreateModal: () => set({ isCreateModalOpen: true }),

closeCreateModal: () => set({ isCreateModalOpen: false }),

reset: () =>
set({
selectedExampleId: null,
searchQuery: '',
filter: initialFilter,
isCreateModalOpen: false,
}),
}));

/**
* 사용 예시:
*
* ```tsx
* import { useExampleStore } from '@/features/example/store/useExampleStore';
*
* function ExampleScreen() {
* const { selectedExampleId, setSelectedExampleId } = useExampleStore();
*
* return (
* <Button onPress={() => setSelectedExampleId('example-123')}>
* 예제 선택
* </Button>
* );
* }
* ```
*/

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 예제(Example) 관련 스토어가 있습니다. 이 스토어는 택시 매칭이나 다른 도메인의 클라이언트 상태를 관리하는 것으로 보이며, weather 도메인과 무관합니다. weather 폴더에는 날씨 관련 상태(예: 선택된 지역, 날씨 표시 단위 등)만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +1 to +107
// 예제: 비즈니스 로직 (복잡한 계산, 검증 등)

/**
* 두 날짜 사이의 차이를 사람이 읽기 쉬운 형식으로 반환
*
* @param dateString ISO 8601 형식의 날짜 문자열
* @returns "방금 전", "3분 전", "2시간 전" 등
*
* @example
* ```ts
* getTimeAgo('2024-01-01T10:00:00Z'); // "2시간 전"
* ```
*/
export function getTimeAgo(dateString: string): string {
const date = new Date(dateString);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);

if (diffMins < 1) return '방금 전';
if (diffMins < 60) return `${diffMins}분 전`;
if (diffHours < 24) return `${diffHours}시간 전`;
if (diffDays < 7) return `${diffDays}일 전`;

// 7일 이상이면 날짜 표시
return date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}

/**
* 택시 요금 계산 (기본 요금 + 거리 요금)
*
* @param distanceKm 거리 (km)
* @param participants 탑승 인원
* @returns 1인당 예상 요금 (원)
*
* @example
* ```ts
* calculateFare(5, 3); // 6600 (5km, 3명 탑승)
* ```
*/
export function calculateFare(distanceKm: number, participants: number): number {
const BASE_FARE = 4800; // 기본 요금
const DISTANCE_FARE_PER_KM = 1000; // km당 요금

const totalFare = BASE_FARE + distanceKm * DISTANCE_FARE_PER_KM;
const farePerPerson = Math.ceil(totalFare / participants);

return farePerPerson;
}

/**
* 제목 유효성 검증
*
* @param title 검증할 제목
* @returns 유효하면 true, 아니면 에러 메시지
*
* @example
* ```ts
* validateTitle('금정역 → 한세대'); // true
* validateTitle(''); // '제목을 입력해주세요.'
* validateTitle('a'); // '제목은 2자 이상이어야 합니다.'
* ```
*/
export function validateTitle(title: string): true | string {
if (!title.trim()) {
return '제목을 입력해주세요.';
}

if (title.trim().length < 2) {
return '제목은 2자 이상이어야 합니다.';
}

if (title.length > 50) {
return '제목은 50자 이하여야 합니다.';
}

return true;
}

/**
* 출발 시간이 현재 시간보다 미래인지 검증
*
* @param departureTime ISO 8601 형식의 날짜 문자열
* @returns 유효하면 true, 아니면 에러 메시지
*
* @example
* ```ts
* validateDepartureTime('2099-12-31T23:59:59Z'); // true
* validateDepartureTime('2020-01-01T00:00:00Z'); // '출발 시간은 현재 시간보다 이후여야 합니다.'
* ```
*/
export function validateDepartureTime(departureTime: string): true | string {
const departure = new Date(departureTime);
const now = new Date();

if (departure <= now) {
return '출발 시간은 현재 시간보다 이후여야 합니다.';
}

return true;
}

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weather 기능 폴더에 택시 요금 계산(calculateFare) 등 예제 비즈니스 로직이 포함되어 있습니다. 이 로직들은 weather 도메인과 전혀 관련이 없습니다. weather 폴더에는 날씨 관련 유틸리티 함수(예: 온도 변환, 날씨 상태 판단 등)만 있어야 합니다. 파일을 삭제하거나 적절한 도메인 폴더로 이동해야 합니다.

Copilot generated this review using guidance from repository custom instructions.

@tnemnorivnelee tnemnorivnelee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예제 코드들만 제거하면 될 것 같습니다!

tnemnorivnelee
tnemnorivnelee previously approved these changes Jan 2, 2026

@tnemnorivnelee tnemnorivnelee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!

Yeram9286
Yeram9286 previously approved these changes Jan 5, 2026

@Yeram9286 Yeram9286 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!

@soyeoung1 soyeoung1 dismissed stale reviews from Yeram9286 and tnemnorivnelee via fd5d7bb January 5, 2026 14:24

@Yeram9286 Yeram9286 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

죄송합니다 ㅠㅠ 승인하는것을 깜빡했습니다 고생하셨습니다

@soyeoung1 soyeoung1 merged commit 22dabe9 into develop Jan 17, 2026
2 checks passed
@soyeoung1 soyeoung1 deleted the refactor/home-feature-split branch January 17, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] Home feature 컴포넌트 도메인별 분리

4 participants