Skip to content

Commit

Permalink
feat: Add travel-schedule slice (#14)
Browse files Browse the repository at this point in the history
* feat: Add type declarations related to travel schedule

* fix: Remove index file in features

* chore: Modify Lint and Format Github actions (#9)

* chore: Update scripts in package.json

* chore: Update lint-format.yml

* chore: Update lint-format.yml

* chore: Add uuid

* feat: Add type declarations related to travel schedule

* feat: Add actions and slices related to travel schedule

* feat: Add `TravelScheduleProvider` and apply

* comment: Remove console.log

* test: Add test for travel schedule logic

* chore: Create run-test.yml (#13)

* chore: Create run-test.yml

* chore: Update package.json

* chore: Update lint-format.yml

* chore: auto-fix linting and formatting issues

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix: Modify parameter types in DaySchedule methods

* feat: Add `updateDestination()` action

* chore: Update eslint.config.mjs

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
cjeongmin and github-actions[bot] committed Aug 17, 2024
1 parent fa5c6c3 commit 3543669
Show file tree
Hide file tree
Showing 11 changed files with 367 additions and 6 deletions.
7 changes: 7 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,11 @@ export default [
'import/no-default-export': 'off',
},
},
{
files: ['src/app/layout.tsx'],

rules: {
'react/jsx-max-depth': 'off',
},
},
];
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
},
"dependencies": {
"@emotion/styled": "^11.13.0",
"@types/uuid": "^10.0.0",
"axios": "^1.7.2",
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"uuid": "^10.0.0",
"zustand": "^4.5.4"
},
"devDependencies": {
Expand Down
103 changes: 103 additions & 0 deletions src/__test__/travel-schedule-actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
createTravelScheduleStore,
Destination,
} from '@/features/travel-schedule';

describe('여행 관련 일정 로직 테스트', () => {
test('초기 상태는 반드시 비어 있는 배열이어야 합니다', () => {
const store = createTravelScheduleStore();

const schedules = store.getState().schedules;

expect(schedules.length).toEqual(0);
});

test('여행 일자를 추가하였을 때, 정상적으로 추가되어야 합니다', () => {
const store = createTravelScheduleStore();

const { schedules, addDaySchedule } = store.getState();
addDaySchedule();

expect(store.getState().schedules.length).not.toEqual(schedules.length);
});

test('목적지를 추가하였을 때, 정상적으로 추가되어야 합니다', () => {
const store = createTravelScheduleStore();

const destination: Destination = {
id: 'test-id',
name: 'test-name',
timeToDestination: -1,
startDate: new Date(),
endDate: new Date(),
};
const { addDaySchedule, addDestination } = store.getState();
addDaySchedule();
addDestination({
day: 0,
destination,
});

const target = store
.getState()
.schedules[0].destinations.find(({ id }) => id === 'test-id');
expect(target).not.toBeUndefined();
});

test('목적지를 삭제하였을 때, 정상적으로 제거되어야 합니다', () => {
const store = createTravelScheduleStore();

const destination: Destination = {
id: 'test-id',
name: 'test-name',
timeToDestination: -1,
startDate: new Date(),
endDate: new Date(),
};
const { addDaySchedule, addDestination, removeDestination } =
store.getState();
addDaySchedule();
addDestination({
day: 0,
destination,
});
removeDestination({ day: 0, destination });

const target = store
.getState()
.schedules[0].destinations.find(({ id }) => id === 'test-id');
expect(target).toBeUndefined();
});

test('목적지를 수정했을 때, 정상적으로 변경되어야 합니다', () => {
const store = createTravelScheduleStore();

const destination: Destination = {
id: 'test-id',
name: 'test-name',
timeToDestination: -1,
startDate: new Date(),
endDate: new Date(),
};
const { addDaySchedule, addDestination, updateDestination } =
store.getState();
addDaySchedule();
addDestination({
day: 0,
destination,
});
updateDestination({
day: 0,
target: destination,
updateValue: {
...destination,
name: 'changed-name',
},
});

const target = store
.getState()
.schedules[0].destinations.find(({ id }) => id === 'test-id');
expect(target).not.toEqual(destination);
});
});
15 changes: 9 additions & 6 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

import './globals.css';
import { NavigationBar, Header } from '@/components';
import { TravelScheduleStoreProvider } from '@/providers';

const inter = Inter({ subsets: ['latin'] });

Expand All @@ -19,11 +20,13 @@ export default function RootLayout({
return (
<html lang='ko-kr'>
<body className={inter.className}>
<main>
<Header />
{children}
<NavigationBar />
</main>
<TravelScheduleStoreProvider>
<main>
<Header />
{children}
<NavigationBar />
</main>
</TravelScheduleStoreProvider>
</body>
</html>
);
Expand Down
Empty file removed src/features/index.ts
Empty file.
2 changes: 2 additions & 0 deletions src/features/travel-schedule/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './travel-schedule.model';
export * from './travel-schedule.slice';
57 changes: 57 additions & 0 deletions src/features/travel-schedule/travel-schedule.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export interface Destination {
id: string;
name: string;
timeToDestination: number;
startDate: Date;
endDate: Date;
}

export class DaySchedule {
destinations: Destination[];

constructor({ destinations }: Pick<DaySchedule, 'destinations'>) {
this.destinations = destinations;
}

addDestination({ destination }: { destination: Destination }) {
this.destinations.push(destination);
this.sortByStartTime();
}

removeDestination({ destinationId }: { destinationId: Destination['id'] }) {
this.destinations = this.destinations.filter(
({ id }) => id !== destinationId,
);
}

updateDestination({
destinationId,
updateValue,
}: {
destinationId: Destination['id'];
updateValue: Partial<Destination>;
}) {
this.destinations = this.destinations.map((destination) =>
destination.id === destinationId
? { ...destination, ...updateValue }
: destination,
);
this.sortByStartTime();
}

sortByStartTime() {
this.destinations.sort((lhs, rhs) => {
const [lhsStartTime, rhsStartTime] = [
lhs.startDate.getTime(),
rhs.startDate.getTime(),
];
if (lhsStartTime < rhsStartTime) return -1;
else if (lhsStartTime === rhsStartTime) return 0;
return 1;
});
}
}

export interface TravelSchedule {
schedules: DaySchedule[];
}
115 changes: 115 additions & 0 deletions src/features/travel-schedule/travel-schedule.slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { createStore } from 'zustand';

import {
DaySchedule,
Destination,
TravelSchedule,
} from './travel-schedule.model';

export type TravelScheduleState = TravelSchedule;

export interface TravelScheduleActions {
addDaySchedule: () => void;

addDestination: (params: { day: number; destination: Destination }) => void;

removeDestination: (params: {
day: number;
destination: Destination;
}) => void;

updateDestination: (params: {
day: number;
target: Destination;
updateValue: Destination;
}) => void;

reset: () => void;
}

export type TravelScheduleStore = TravelScheduleState & TravelScheduleActions;

export const defaultInitState: TravelScheduleState = {
schedules: [],
};

type Setter = (
partial:
| TravelScheduleStore
| Partial<TravelScheduleStore>
| ((
state: TravelScheduleStore,
) => TravelScheduleStore | Partial<TravelScheduleStore>),
replace?: boolean | undefined,
) => void;

const addDaySchedule = ({ set }: { set: Setter }) =>
set((state) => ({
schedules: [...state.schedules, new DaySchedule({ destinations: [] })],
}));

const addDestination = ({
day,
destination,
set,
}: {
day: number;
destination: Destination;
set: Setter;
}) =>
set((state) => {
const result = [...state.schedules];
result[day].addDestination({ destination });
return { schedules: result };
});

const removeDestination = ({
day,
destination,
set,
}: {
day: number;
destination: Destination;
set: Setter;
}) =>
set((state) => {
const result = [...state.schedules];
result[day].removeDestination({ destinationId: destination.id });
return { schedules: result };
});

const updateDestination = ({
day,
target,
updateValue,
set,
}: {
day: number;
target: Destination;
updateValue: Destination;
set: Setter;
}) =>
set((state) => {
const result = [...state.schedules];
result[day].updateDestination({
destinationId: target.id,
updateValue: updateValue,
});
return { schedules: result };
});

export const createTravelScheduleStore = (
initState: TravelScheduleState = defaultInitState,
) => {
return createStore<TravelScheduleStore>()((set) => ({
...initState,
addDaySchedule: () => addDaySchedule({ set }),
addDestination: ({ day, destination }) =>
addDestination({ day, destination, set }),
removeDestination: ({ day, destination }) =>
removeDestination({ day, destination, set }),
updateDestination: ({ day, target, updateValue }) =>
updateDestination({ day, target, updateValue, set }),
reset: () => set(defaultInitState),
}));
};
Loading

0 comments on commit 3543669

Please sign in to comment.