-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #99 from softeerbootcamp4th/feature/29-admin-event…
…-create [feat] 이벤트 생성 컴포넌트 리듀서 추가, 일부 기능구현
- Loading branch information
Showing
22 changed files
with
915 additions
and
66 deletions.
There are no files selected for viewing
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
This file was deleted.
Oops, something went wrong.
116 changes: 116 additions & 0 deletions
116
src/adminPage/features/eventEdit/DateTimeRangeInput.jsx
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,116 @@ | ||
import { Input } from "@admin/components/SmallInput.jsx"; | ||
import { formatDate } from "@common/utils.js"; | ||
|
||
function dateToSplittedState(date) { | ||
if (date === null || date === "" || date === undefined) return ["", ""]; | ||
return formatDate(date, "YYYY-MM-DD hh:mm").split(" "); | ||
} | ||
|
||
function applyDateInputToDateObj(inputValue, timeValue) { | ||
if (inputValue === "") return null; | ||
const [y, m, d] = inputValue.split("-").map(Number); | ||
|
||
let date = | ||
timeValue === null ? new Date("1970.1.1 00:00") : new Date(timeValue); | ||
|
||
date.setFullYear(y); | ||
date.setMonth(m - 1); | ||
date.setDate(d); | ||
return date; | ||
} | ||
|
||
function applyTimeInputToDateObj(inputValue, timeValue) { | ||
if (inputValue === "") return null; | ||
const [h, m] = inputValue.split(":").map(Number); | ||
|
||
let date = timeValue === null ? new Date() : new Date(timeValue); | ||
|
||
date.setHours(h); | ||
date.setMinutes(Math.round(m / 5) * 5); | ||
return date; | ||
} | ||
|
||
function DateTimeRangeInput({ | ||
range = [null, null], | ||
setRange, | ||
wrapperClass, | ||
inputClass, | ||
required, | ||
} = {}) { | ||
const [startDate, startTime] = dateToSplittedState(range[0]); | ||
const [endDate, endTime] = dateToSplittedState(range[1]); | ||
|
||
function setStartDate(value) { | ||
if (value === "") return setRange([null, range[1]]); | ||
let date = applyDateInputToDateObj(value, range[0]); | ||
if (range[1] === null || date <= range[1]) setRange([date, range[1]]); | ||
else setRange([range[1], range[1]]); | ||
} | ||
|
||
function setStartTime(value) { | ||
if (value === "") return setRange([null, range[1]]); | ||
let date = applyTimeInputToDateObj(value, range[0]); | ||
if (range[1] === null || date <= range[1]) setRange([date, range[1]]); | ||
else setRange([range[1], range[1]]); | ||
} | ||
|
||
function setEndDate(value) { | ||
if (value === "") return setRange([range[0], null]); | ||
let date = applyDateInputToDateObj(value, range[1]); | ||
if (range[0] === null || date >= range[0]) setRange([range[0], date]); | ||
else setRange([range[0], range[0]]); | ||
} | ||
|
||
function setEndTime(value) { | ||
if (value === "") return setRange([range[0], null]); | ||
let date = applyTimeInputToDateObj(value, range[1]); | ||
if (range[0] === null || date >= range[0]) setRange([range[0], date]); | ||
else setRange([range[0], range[0]]); | ||
} | ||
|
||
return ( | ||
<div className={wrapperClass}> | ||
<div className="flex gap-4"> | ||
<Input | ||
className={inputClass} | ||
text={startDate} | ||
setText={setStartDate} | ||
type="date" | ||
name="startDate" | ||
required={required} | ||
/> | ||
<Input | ||
className={inputClass} | ||
text={startTime} | ||
setText={setStartTime} | ||
type="time" | ||
name="startTime" | ||
step="300" | ||
required={required} | ||
/> | ||
</div> | ||
~ | ||
<div className="flex gap-4"> | ||
<Input | ||
className={inputClass} | ||
text={endDate} | ||
setText={setEndDate} | ||
type="date" | ||
name="endDate" | ||
required={required} | ||
/> | ||
<Input | ||
className={inputClass} | ||
text={endTime} | ||
setText={setEndTime} | ||
type="time" | ||
name="endTime" | ||
step="300" | ||
required={required} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default DateTimeRangeInput; |
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,5 @@ | ||
function DrawInput() { | ||
return <>추첨 이벤트임</>; | ||
} | ||
|
||
export default DrawInput; |
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 { Input, TextBox } from "@admin/components/SmallInput.jsx"; | ||
import DateTimeRangeInput from "./DateTimeRangeInput.jsx"; | ||
|
||
function EventBaseDataInput({ state, dispatch, mode }) { | ||
const columnsStyle = "grid grid-cols-[6rem_1fr] items-center gap-2"; | ||
const NAME_MAX_LENGTH = 40; | ||
const DESCRIPTION_MAX_LENGTH = 100; | ||
|
||
return ( | ||
<> | ||
<label className={columnsStyle}> | ||
<span className="text-center"> | ||
이벤트 명<sup className="text-red-500">*</sup> | ||
</span> | ||
<div className="w-[25rem] h-8 relative flex items-center"> | ||
<Input | ||
className="w-full h-full" | ||
text={state.name} | ||
setText={(value) => dispatch({ type: "set_name", value })} | ||
required | ||
maxLength="40" | ||
pattern={ | ||
"^[ㄱ-ㅎ가-힣A-Za-z0-9~!.,\\[\\]\\(\\):\\-_%*\\/+#$@'\"=\\s]*$" | ||
} | ||
/> | ||
<span | ||
className={`absolute right-3 text-detail-l ${state.name.length >= NAME_MAX_LENGTH ? "text-red-500" : "text-neutral-600"}`} | ||
> | ||
{state.name.length}/{NAME_MAX_LENGTH} | ||
</span> | ||
</div> | ||
</label> | ||
<label className={columnsStyle}> | ||
<span className="text-center">이벤트 ID</span> | ||
<Input | ||
className="w-[25rem] h-8" | ||
defaultValue={state.eventId} | ||
disabled | ||
/> | ||
</label> | ||
<label className={columnsStyle}> | ||
<span className="text-center"> | ||
이벤트 프레임<sup className="text-red-500">*</sup> | ||
</span> | ||
<Input | ||
className="w-[25rem] h-8" | ||
text={state.eventFrameId} | ||
setText={(value) => dispatch({ type: "set_event_frame", value })} | ||
required | ||
disabled={mode === "edit"} | ||
/> | ||
</label> | ||
<label className={columnsStyle}> | ||
<span className="text-center"> | ||
이벤트 기간<sup className="text-red-500">*</sup> | ||
</span> | ||
<DateTimeRangeInput | ||
range={[state.startTime, state.endTime]} | ||
setRange={(range) => { | ||
dispatch({ type: "set_date_range", value: range }); | ||
}} | ||
wrapperClass="flex gap-2 items-center flex-wrap" | ||
inputClass="w-48 h-8" | ||
required | ||
/> | ||
</label> | ||
<label className="grid grid-cols-[6rem_1fr] items-start gap-2"> | ||
<span className="text-center">이벤트 요약</span> | ||
<div className="relative"> | ||
<TextBox | ||
className="w-full" | ||
text={state.description} | ||
setText={(value) => dispatch({ type: "set_description", value })} | ||
rows="4" | ||
maxLength="100" | ||
/> | ||
<span | ||
className={`absolute right-3 bottom-3 text-detail-l ${state.description.length >= DESCRIPTION_MAX_LENGTH ? "text-red-500" : "text-neutral-600"}`} | ||
> | ||
{state.description.length}/{DESCRIPTION_MAX_LENGTH} | ||
</span> | ||
</div> | ||
</label> | ||
<label className={columnsStyle}> | ||
<span className="text-center">이벤트 URL</span> | ||
<Input | ||
className="w-[25rem] h-8" | ||
text={state.url} | ||
setText={(value) => dispatch({ type: "set_url", value })} | ||
type="url" | ||
pattern="https?://.*" | ||
/> | ||
</label> | ||
</> | ||
); | ||
} | ||
|
||
export default EventBaseDataInput; |
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,56 @@ | ||
import FcfsInput from "./FcfsInput.jsx"; | ||
import DrawInput from "./DrawInput.jsx"; | ||
|
||
function EventTypeSelector({ selected, onClick, children }) { | ||
const selectorBaseStyle = `flex justify-center items-center px-8 py-2 rounded-t-lg text-body-m relative hover:bg-blue-50`; | ||
const selectedStyle = `text-blue-400 font-bold after:w-full after:h-1 after:absolute after:-bottom-px after:border-b-2 after:border-blue-400`; | ||
const unSelectedStyle = `text-neutral-800 hover:text-blue-400`; | ||
|
||
return ( | ||
<button | ||
type="button" | ||
className={`${selectorBaseStyle} ${selected ? selectedStyle : unSelectedStyle}`} | ||
onClick={onClick} | ||
> | ||
{children} | ||
</button> | ||
); | ||
} | ||
|
||
function EventDetailInput({ state, dispatch, mode }) { | ||
function selectEventType(type) { | ||
return () => { | ||
if (mode === "edit") return; | ||
dispatch({ type: "set_event_type", value: type }); | ||
}; | ||
} | ||
|
||
return ( | ||
<div className="w-full"> | ||
<div className="flex w-full border-b border-neutral-200"> | ||
<EventTypeSelector | ||
selected={state.eventType === "fcfs"} | ||
onClick={selectEventType("fcfs")} | ||
> | ||
선착순 | ||
</EventTypeSelector> | ||
<EventTypeSelector | ||
selected={state.eventType === "draw"} | ||
onClick={selectEventType("draw")} | ||
> | ||
추첨 | ||
</EventTypeSelector> | ||
</div> | ||
<div className="flex-grow flex justify-center items-center p-4"> | ||
{state.eventType === "fcfs" && ( | ||
<FcfsInput state={state} dispatch={dispatch} /> | ||
)} | ||
{state.eventType === "draw" && ( | ||
<DrawInput state={state} dispatch={dispatch} /> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default EventDetailInput; |
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,12 @@ | ||
import EventEditor from "./index.jsx"; | ||
import { useQuery } from "@common/dataFetch/getQuery.js"; | ||
import { fetchServer } from "@common/dataFetch/fetchServer.js"; | ||
|
||
function EventEditFetcher({ eventId }) { | ||
const data = useQuery(`event-detail-${eventId}`, () => | ||
fetchServer(`/api/v1/admin/events/${eventId}`), | ||
); | ||
return <EventEditor mode="edit" initialData={data} />; | ||
} | ||
|
||
export default EventEditFetcher; |
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,5 @@ | ||
function FcfsInput() { | ||
return <>선착순 이벤트임</>; | ||
} | ||
|
||
export default FcfsInput; |
33 changes: 33 additions & 0 deletions
33
src/adminPage/features/eventEdit/businessLogic/DrawGradeData.js
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,33 @@ | ||
class DrawGradeData { | ||
constructor(rawData) { | ||
if (rawData == null) this.data = [{ grade: 1, count: 0, prizeInfo: "" }]; | ||
else this.data = [...rawData].sort((a, b) => a.grade - b.grade); | ||
} | ||
modify(data) { | ||
const newData = new DrawGradeData(this.data); | ||
const key = data.grade - 1; | ||
newData.data[key] = { ...this.data[key], ...data }; | ||
return newData; | ||
} | ||
adjustCount(count) { | ||
if (count < 1) return this; | ||
const originLength = this.data.length; | ||
if (count === originLength) return this; | ||
|
||
const newData = new DrawGradeData(this.data); | ||
if (count > originLength) { | ||
for (let i = originLength; i < count; i++) { | ||
newData.data.push({ grade: i + 1, count: 0, prizeInfo: "" }); | ||
} | ||
} else newData.data.splice(count, Infinity); | ||
return newData; | ||
} | ||
*[Symbol.iterator]() { | ||
yield* this.data; | ||
} | ||
toJSON() { | ||
return this.data; | ||
} | ||
} | ||
|
||
export default DrawGradeData; |
Oops, something went wrong.