Skip to content

Commit 4b6554e

Browse files
authored
Merge pull request #301 from mash-up-kr/develop
Main Release/1.8.0
2 parents 34ed0ab + a311aeb commit 4b6554e

File tree

14 files changed

+231
-8
lines changed

14 files changed

+231
-8
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
BASE_URL=
2+
KAKAO_KEY=
Binary file not shown.

@types/custom-types/global.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export {};
2+
3+
declare global {
4+
interface Window {
5+
daum: any;
6+
}
7+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@testing-library/react": "^12.1.2",
4747
"@testing-library/react-hooks": "^7.0.2",
4848
"@testing-library/user-event": "^13.5.0",
49+
"@types/daum-postcode": "^2.0.3",
4950
"@types/editorjs__header": "^2.6.0",
5051
"@types/jest": "^27.4.0",
5152
"@types/lodash-es": "^4.17.6",

src/components/Schedule/ScheduleTemplate/ScheduleTemplate.component.tsx

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import React, { useMemo } from 'react';
22
import { useFieldArray, useFormContext } from 'react-hook-form';
33
import { useRecoilValue } from 'recoil';
4-
import { DatePickerField, InputField, SelectField } from '@/components';
4+
import { Button, DatePickerField, InputField, RadioButtonField, SelectField } from '@/components';
55
import { InputSize } from '@/components/common/Input/Input.component';
66
import * as Styled from './ScheduleTemplate.styled';
77
import { $generations } from '@/store';
88
import { SelectOption } from '@/components/common/Select/Select.component';
99
import { SessionTemplate } from '../SessionTemplate';
1010
import Plus from '@/assets/svg/plus-20.svg';
1111
import { EventCreateRequest } from '@/types';
12-
import { ScheduleFormValues } from '@/utils';
12+
import { LocationType, ScheduleFormValues } from '@/utils';
13+
import { useScript } from '@/hooks';
1314

1415
const DEFAULT_SESSION: EventCreateRequest = {
1516
startedAt: '',
@@ -18,15 +19,23 @@ const DEFAULT_SESSION: EventCreateRequest = {
1819
contentsCreateRequests: [],
1920
};
2021

22+
const DAUM_POSTCODE_SCRIPT = '//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js';
23+
const DAUM_POSTCODE_API_URL = 'https://dapi.kakao.com/v2/local/search/address';
24+
2125
const ScheduleTemplate = () => {
22-
const { register, control, formState, getValues } = useFormContext<ScheduleFormValues>();
26+
const { register, control, formState, getValues, watch, setValue } =
27+
useFormContext<ScheduleFormValues>();
2328
const generations = useRecoilValue($generations);
2429

30+
const locationType = watch('locationType');
31+
2532
const { fields, append, remove } = useFieldArray({
2633
name: 'sessions',
2734
control,
2835
});
2936

37+
useScript(DAUM_POSTCODE_SCRIPT);
38+
3039
const generationOptions = useMemo<SelectOption[]>(() => {
3140
return generations.map(({ generationNumber }) => ({
3241
label: `${generationNumber}기`,
@@ -37,6 +46,36 @@ const ScheduleTemplate = () => {
3746
const defaultOption = generationOptions.find(
3847
(option) => option.value === getValues('generationNumber')?.toString(),
3948
);
49+
const handleClickAddressSearch = () => {
50+
new window.daum.Postcode({
51+
async oncomplete(data: { address: string }) {
52+
const res = await fetch(`${DAUM_POSTCODE_API_URL}?query=${data.address}`, {
53+
method: 'GET',
54+
headers: {
55+
'Content-Type': 'application/json',
56+
Authorization: `KakaoAK ${process.env.KAKAO_KEY}`,
57+
},
58+
});
59+
const json = await res.json();
60+
61+
const {
62+
address_name: address,
63+
x: longitude,
64+
y: latitude,
65+
building_name: buildingName,
66+
} = json.documents[0].road_address;
67+
const placeName = buildingName || address;
68+
69+
setValue('locationInfo', {
70+
address,
71+
latitude,
72+
longitude,
73+
placeName,
74+
});
75+
setValue('placeName', placeName);
76+
},
77+
}).open();
78+
};
4079

4180
return (
4281
<>
@@ -66,6 +105,45 @@ const ScheduleTemplate = () => {
66105
defaultDate={getValues('date')}
67106
{...register('date', { required: true })}
68107
/>
108+
<div>
109+
<Styled.InputLabel htmlFor="location">
110+
<span>장소</span>
111+
<Styled.RequiredDot />
112+
</Styled.InputLabel>
113+
<Styled.RadioButtonGroup>
114+
<RadioButtonField
115+
label="오프라인"
116+
required
117+
value={LocationType.OFFLINE}
118+
{...register('locationType', { required: true })}
119+
/>
120+
<RadioButtonField
121+
label="온라인"
122+
required
123+
value={LocationType.ONLINE}
124+
{...register('locationType', { required: true })}
125+
/>
126+
</Styled.RadioButtonGroup>
127+
{locationType === LocationType.OFFLINE && (
128+
<Styled.LocationWrapper>
129+
<Styled.InputWithButton>
130+
<InputField
131+
$size="md"
132+
placeholder="장소"
133+
{...register('placeName', { required: locationType === LocationType.OFFLINE })}
134+
/>
135+
<Button shape="primaryLine" $size="md" onClick={handleClickAddressSearch}>
136+
주소 검색
137+
</Button>
138+
</Styled.InputWithButton>
139+
<InputField
140+
$size="md"
141+
placeholder="상세 주소를 입력해 주세요 (ex. 동, 호, 층 등)"
142+
{...register('detailAddress')}
143+
/>
144+
</Styled.LocationWrapper>
145+
)}
146+
</div>
69147
</Styled.ScheduleContent>
70148
<Styled.SessionContent>
71149
<Styled.Title>세션 정보</Styled.Title>

src/components/Schedule/ScheduleTemplate/ScheduleTemplate.styled.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export const ScheduleContent = styled.div`
66
display: flex;
77
flex-direction: column;
88
gap: 2rem;
9-
height: 36.9rem;
109
padding: 2.4rem;
1110
background-color: ${theme.colors.white};
1211
border: 0.1rem solid ${theme.colors.gray30};
@@ -43,3 +42,38 @@ export const AddButton = styled.button`
4342
margin-top: 2.4rem;
4443
background-color: transparent;
4544
`;
45+
46+
export const InputLabel = styled.label`
47+
${({ theme }) => css`
48+
${theme.fonts.medium15}
49+
display: flex;
50+
margin-bottom: 0.6rem;
51+
color: ${theme.colors.gray70};
52+
`}
53+
`;
54+
55+
export const RequiredDot = styled.span`
56+
width: 0.6rem;
57+
min-width: 0.6rem;
58+
height: 0.6rem;
59+
margin: 0.8rem 0 0 0.6rem;
60+
background-color: #eb6963;
61+
border-radius: 50%;
62+
`;
63+
64+
export const RadioButtonGroup = styled.div`
65+
display: flex;
66+
gap: 2rem;
67+
margin-bottom: 0.6rem;
68+
`;
69+
70+
export const InputWithButton = styled.div`
71+
display: flex;
72+
gap: 1rem;
73+
`;
74+
75+
export const LocationWrapper = styled.div`
76+
display: flex;
77+
flex-direction: column;
78+
gap: 0.6rem;
79+
`;

src/components/ScheduleDetail/ScheduleInfoList/ScheduleInfoList.component.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ interface ScheduleInfoListProps {
1111
startedAt: string;
1212
publishedAt?: string;
1313
status: ValueOf<typeof ScheduleStatus>;
14+
location: {
15+
address: string | null;
16+
placeName: string;
17+
};
1418
}
1519

1620
const ScheduleInfoList = ({
@@ -20,6 +24,7 @@ const ScheduleInfoList = ({
2024
createdAt,
2125
publishedAt,
2226
status,
27+
location,
2328
}: ScheduleInfoListProps) => {
2429
const scheduleInfoListItem = useMemo(() => {
2530
return [
@@ -39,6 +44,13 @@ const ScheduleInfoList = ({
3944
label: '등록 일시',
4045
value: formatDate(createdAt, 'YYYY년 M월 D일 A hh시 mm분'),
4146
},
47+
{
48+
label: '장소',
49+
value:
50+
location.address === null
51+
? location.placeName
52+
: `${location.placeName}, ${location.address}`,
53+
},
4254
{
4355
label: '배포 일시',
4456
value: formatDate(publishedAt, 'YYYY년 M월 D일 A hh시 mm분'),
@@ -48,7 +60,7 @@ const ScheduleInfoList = ({
4860
value: getScheduleStatusText(status),
4961
},
5062
];
51-
}, [createdAt, generationNumber, name, publishedAt, startedAt, status]);
63+
}, [createdAt, generationNumber, name, publishedAt, startedAt, status, location]);
5264

5365
return (
5466
<Styled.ScheduleInfoList>

src/components/common/RadioButton/RadioButton.styled.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ export const RadioButtonMark = styled.span`
6767
export const RadioButtonText = styled.span`
6868
${({ theme }) => css`
6969
${theme.fonts.medium14}
70-
padding-left: 3.8rem;
70+
padding-left: 3rem;
7171
`}
7272
`;

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export { default as useHistory } from './useHistory';
1313
export { default as usePrompt } from './usePrompt';
1414
export { default as useMyTeam } from './useMyTeam';
1515
export { default as useRefreshSelectorFamilyByKey } from './useRefreshSelectorFamilyByKey';
16+
export { default as useScript } from './useScript';

src/hooks/useScript.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useEffect } from 'react';
2+
3+
const useScript = (url: string) => {
4+
useEffect(() => {
5+
const script = document.createElement('script');
6+
script.src = url;
7+
script.async = true;
8+
document.body.appendChild(script);
9+
return () => {
10+
document.body.removeChild(script);
11+
};
12+
}, [url]);
13+
};
14+
15+
export default useScript;

src/pages/ScheduleDetail/ScheduleDetail.page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const ScheduleDetail = () => {
2929
publishedAt,
3030
status,
3131
eventList: sessionList,
32+
location,
3233
} = useRecoilValue($scheduleDetail({ scheduleId: scheduleId ?? '' }));
3334

3435
const isPublished = status === ScheduleStatus.PUBLIC;
@@ -147,6 +148,7 @@ const ScheduleDetail = () => {
147148
createdAt={createdAt}
148149
publishedAt={publishedAt}
149150
status={status}
151+
location={location}
150152
/>
151153
</Styled.Content>
152154
<Styled.Content>

src/types/dto/schedule.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export interface ScheduleCreateRequest {
3131
name: string;
3232
startedAt: string;
3333
eventsCreateRequests: EventCreateRequest[];
34+
address?: string;
35+
latitude?: number;
36+
longitude?: number;
37+
placeName?: string;
3438
}
3539

3640
export interface ScheduleUpdateRequest {
@@ -39,6 +43,10 @@ export interface ScheduleUpdateRequest {
3943
name: string;
4044
startedAt: string;
4145
eventsCreateRequests: EventCreateRequest[];
46+
address?: string;
47+
latitude?: number;
48+
longitude?: number;
49+
placeName?: string;
4250
}
4351

4452
export interface ScheduleResponse {
@@ -51,6 +59,12 @@ export interface ScheduleResponse {
5159
publishedAt?: string;
5260
eventList: Session[];
5361
status: ValueOf<typeof ScheduleStatus>;
62+
location: {
63+
address: string | null;
64+
latitude: number | null;
65+
longitude: number | null;
66+
placeName: string;
67+
};
5468
}
5569

5670
export interface QRCodeRequest {

0 commit comments

Comments
 (0)