-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(admin): 옵션 생성, 옵션 삭제 및 옵션 적용 (#206)
* feat(admin): 티켓 옵션 생성하기 * feat(admin): 옵션 생성시 주관식, 객관식 구별 및 미리보기 수정 * feat(admin): 티켓 옵션 삭제하기 * feat(admin) : 티켓에 옵션 적용하기(#203) * yarn install * chore : 티켓 옵션 선택 스타일링 * feat(admin) : 공연 옵션 부착, 제거 dnd * feat(admin) : 공연 옵션 부착, 제거 dnd 스타일 * feat(admin) : 공연 옵션 부착, 제거 dnd 스타일 --------- Co-authored-by: 한규진 <[email protected]>
- Loading branch information
Showing
22 changed files
with
1,212 additions
and
26 deletions.
There are no files selected for viewing
13 changes: 0 additions & 13 deletions
13
apps/admin/src/components/events/options/TempOButtonSet.tsx
This file was deleted.
Oops, something went wrong.
132 changes: 132 additions & 0 deletions
132
apps/admin/src/components/events/options/apply/OptionDropArea.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { Droppable, Draggable } from 'react-beautiful-dnd'; | ||
import styled from '@emotion/styled'; | ||
import { theme, Text, Padding, FlexBox } from '@dudoong/ui'; | ||
import { OptionGroupResponse } from '@lib/apis/option/optionType'; | ||
import { ReactNode } from 'react'; | ||
import { css } from '@emotion/react'; | ||
|
||
interface OptionDropArea { | ||
ticketItemId: number; | ||
optionGroups: OptionGroupResponse[]; | ||
isEditable?: boolean; | ||
} | ||
|
||
const OptionDropArea = ({ | ||
ticketItemId, | ||
optionGroups, | ||
isEditable = true, | ||
}: OptionDropArea) => { | ||
return ( | ||
<> | ||
<Droppable | ||
droppableId={ticketItemId.toString()} | ||
key={ticketItemId.toString()} | ||
> | ||
{(provided) => ( | ||
<section {...provided.droppableProps} ref={provided.innerRef}> | ||
{optionGroups.length === 0 ? ( | ||
<> | ||
<BlankOption | ||
dropPlaceholder={provided.placeholder} | ||
isEditable={isEditable} | ||
/> | ||
</> | ||
) : ( | ||
<> | ||
{optionGroups.map( | ||
(item: OptionGroupResponse, index: number) => ( | ||
<Draggable | ||
draggableId={`ticketOption-${ticketItemId}-${item.optionGroupId}`} | ||
key={`ticketOption-${ticketItemId}-${item.optionGroupId}`} | ||
index={index} | ||
> | ||
{(provided) => ( | ||
<div | ||
{...provided.draggableProps} | ||
{...provided.dragHandleProps} | ||
ref={provided.innerRef} | ||
> | ||
<AppliedOption item={item} isEditable={isEditable} /> | ||
</div> | ||
)} | ||
</Draggable> | ||
), | ||
)} | ||
{provided.placeholder} | ||
</> | ||
)} | ||
</section> | ||
)} | ||
</Droppable> | ||
</> | ||
); | ||
}; | ||
|
||
const AppliedOption = ({ | ||
item, | ||
isEditable, | ||
}: { | ||
item: OptionGroupResponse; | ||
isEditable: boolean; | ||
}) => { | ||
const additionalPrice = | ||
item.options.find((option) => option.answer === '예')?.additionalPrice || | ||
'0원'; | ||
return ( | ||
<OptionWrapper key={item.optionGroupId}> | ||
<Padding size={[16, 21]}> | ||
<FlexBox direction="column" align="flex-start"> | ||
<Text typo="P_Text_16_SB" color="main_500"> | ||
{item.name} | ||
</Text> | ||
<Text typo="Text_14" color="main_400"> | ||
{item.type} {additionalPrice !== '0원' && `· ${additionalPrice}`} | ||
</Text> | ||
</FlexBox> | ||
</Padding> | ||
</OptionWrapper> | ||
); | ||
}; | ||
|
||
const BlankOption = ({ | ||
dropPlaceholder, | ||
isEditable, | ||
}: { | ||
dropPlaceholder: ReactNode; | ||
isEditable: boolean; | ||
}) => { | ||
return ( | ||
<RoundWrapper isEditable={isEditable}> | ||
<Padding size={[40, 38]}> | ||
<Text typo="P_Text_16_M" color="gray_300"> | ||
{isEditable | ||
? '추가할 옵션을 드래그 앤 드롭 해주세요.' | ||
: '이미 판매된 티켓의 옵션은 수정할 수 없어요.'} | ||
</Text> | ||
</Padding> | ||
{dropPlaceholder} | ||
</RoundWrapper> | ||
); | ||
}; | ||
|
||
export default OptionDropArea; | ||
|
||
const RoundWrapper = styled.div<{ isEditable: boolean }>` | ||
border-radius: 10px; | ||
background-color: ${theme.palette.gray_100}; | ||
border: 1px solid ${({ theme }) => theme.palette.gray_200}; | ||
${({ theme, isEditable }) => | ||
!isEditable && | ||
css` | ||
background-color: ${theme.palette.gray_200}; | ||
`} | ||
height: 100px; | ||
margin-top: 10px; | ||
`; | ||
|
||
const OptionWrapper = styled.div` | ||
border-radius: 10px; | ||
background-color: ${theme.palette.main_100}; | ||
height: auto; | ||
margin-top: 12px; | ||
`; |
54 changes: 54 additions & 0 deletions
54
apps/admin/src/components/events/options/apply/OptionItem.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { useLocation } from 'react-router-dom'; | ||
import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||
import OptionApi from '@lib/apis/option/OptionApi'; | ||
import { FlexBox, ListRow, TagButton, Padding } from '@dudoong/ui'; | ||
|
||
export interface OptionItemProps { | ||
name: string; | ||
subText: string; | ||
OptionGroupId: number; | ||
} | ||
|
||
const OptionItem = ({ name, subText, OptionGroupId }: OptionItemProps) => { | ||
const { pathname } = useLocation(); | ||
const eventId = pathname.split('/')[2]; | ||
const queryClient = useQueryClient(); | ||
const { mutate: optionDeleteMutate } = useMutation( | ||
OptionApi.PATCH_OPTION_DELETE, | ||
{ | ||
onSuccess: (data) => { | ||
queryClient.invalidateQueries({ queryKey: ['AllOption', eventId] }); | ||
}, | ||
}, | ||
); | ||
|
||
const handleRemoveClick = () => { | ||
optionDeleteMutate({ | ||
eventId: eventId, | ||
optionGroupId: OptionGroupId, | ||
}); | ||
}; | ||
|
||
return ( | ||
<> | ||
<FlexBox align="center" justify="space-between"> | ||
<ListRow | ||
text={name} | ||
textColor={['black', 'gray_400']} | ||
subText={subText} | ||
padding={[13.5, 23]} | ||
/> | ||
<Padding size={[0, 24, 0, 0]}> | ||
<TagButton | ||
text="삭제" | ||
color="warn" | ||
size="lg" | ||
onClick={handleRemoveClick} | ||
/> | ||
</Padding> | ||
</FlexBox> | ||
</> | ||
); | ||
}; | ||
|
||
export default OptionItem; |
98 changes: 98 additions & 0 deletions
98
apps/admin/src/components/events/options/apply/OptionList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { | ||
ListHeader, | ||
theme, | ||
Spacing, | ||
FlexBox, | ||
Text, | ||
Padding, | ||
} from '@dudoong/ui'; | ||
import styled from '@emotion/styled'; | ||
import OptionItem from './OptionItem'; | ||
import { Draggable, Droppable } from 'react-beautiful-dnd'; | ||
import type { OptionGroupResponse } from '@lib/apis/option/optionType'; | ||
|
||
interface OptionListProps { | ||
optionItems: OptionGroupResponse[]; | ||
} | ||
|
||
const OptionList = ({ optionItems }: OptionListProps) => { | ||
console.log(optionItems); | ||
if (!optionItems?.length) { | ||
return ( | ||
<Wrapper> | ||
<div> | ||
<ListHeader padding={0} size="listHeader_18" title="옵션 목록" /> | ||
<Spacing size={42} /> | ||
<OptionItemContainer> | ||
<Padding size={[24, 12, 24, 12]}> | ||
<Text typo="P_Header_16_SB">옵션을 먼저 생성해주세요!</Text> | ||
</Padding> | ||
</OptionItemContainer> | ||
</div> | ||
</Wrapper> | ||
); | ||
} else { | ||
return ( | ||
<Wrapper> | ||
<FlexBox direction="column" align="flex-start"> | ||
<div> | ||
<ListHeader padding={0} size="listHeader_18" title="옵션 목록" /> | ||
<Spacing size={42} /> | ||
</div> | ||
|
||
<Droppable droppableId="options"> | ||
{(provided) => ( | ||
<section {...provided.droppableProps} ref={provided.innerRef}> | ||
{optionItems?.map((item, index) => ( | ||
<Draggable | ||
key={`eventOption-${item.optionGroupId}`} | ||
draggableId={`eventOption-${item.optionGroupId}`} | ||
index={index} | ||
> | ||
{(provided) => ( | ||
<div | ||
{...provided.draggableProps} | ||
{...provided.dragHandleProps} | ||
ref={provided.innerRef} | ||
> | ||
<OptionItemContainer key={item.optionGroupId}> | ||
<OptionItem | ||
name={item.name} | ||
subText={`${item.type}`} | ||
OptionGroupId={item.optionGroupId} | ||
/> | ||
</OptionItemContainer> | ||
<Spacing size={16} /> | ||
</div> | ||
)} | ||
</Draggable> | ||
))} | ||
{provided.placeholder} | ||
</section> | ||
)} | ||
</Droppable> | ||
</FlexBox> | ||
</Wrapper> | ||
); | ||
} | ||
}; | ||
|
||
export default OptionList; | ||
|
||
const Wrapper = styled.div` | ||
& > div { | ||
position: sticky; | ||
position: -webkit-sticky; | ||
margin-top: 44px; | ||
top: 36px; | ||
} | ||
`; | ||
|
||
const OptionItemContainer = styled.div` | ||
width: 400px; | ||
height: auto; | ||
box-sizing: border-box; | ||
background-color: ${theme.palette.white}; | ||
border-radius: 12px; | ||
border: 1px solid ${theme.palette.black}; | ||
`; |
55 changes: 55 additions & 0 deletions
55
apps/admin/src/components/events/options/apply/TicketListOption.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { ListHeader, Spacing, theme, Text, Divider } from '@dudoong/ui'; | ||
import styled from '@emotion/styled'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { useLocation } from 'react-router-dom'; | ||
import OptionDropArea from './OptionDropArea'; | ||
import OptionApi from '@lib/apis/option/OptionApi'; | ||
|
||
const TicketListOption = () => { | ||
const { pathname } = useLocation(); | ||
const eventId = pathname.split('/')[2]; | ||
|
||
const { data, isSuccess } = useQuery(['AppliedTicket', eventId], () => | ||
OptionApi.GET_EVENTS_APPLIEDOPTIONGROUPS(eventId), | ||
); | ||
|
||
return ( | ||
<Wrapper> | ||
{isSuccess && ( | ||
<> | ||
<Spacing size={36} /> | ||
<ListHeader padding={0} size="listHeader_18" title="티켓 목록" /> | ||
<Spacing size={42} /> | ||
|
||
{data.appliedOptionGroups?.map((item) => ( | ||
<div key={item?.ticketItemId}> | ||
<TicketItem key={item.ticketItemId}> | ||
<Text typo={'P_Header_16_SB'}>{item.ticketName}</Text> | ||
<Divider line={true} /> | ||
<OptionDropArea | ||
ticketItemId={item.ticketItemId} | ||
optionGroups={item.optionGroups} | ||
/> | ||
</TicketItem> | ||
<Spacing size={16} /> | ||
</div> | ||
))} | ||
</> | ||
)} | ||
</Wrapper> | ||
); | ||
}; | ||
export default TicketListOption; | ||
|
||
const Wrapper = styled.div``; | ||
|
||
const TicketItem = styled.div` | ||
width: 400px; | ||
height: auto; | ||
box-sizing: border-box; | ||
background-color: ${theme.palette.white}; | ||
border-radius: 12px; | ||
border: 1px solid ${theme.palette.black}; | ||
padding: 24px 22px; | ||
`; |
Oops, something went wrong.