Skip to content

Commit

Permalink
Merge pull request #99 from softeerbootcamp4th/feature/29-admin-event…
Browse files Browse the repository at this point in the history
…-create

[feat] 이벤트 생성 컴포넌트 리듀서 추가, 일부 기능구현
  • Loading branch information
darkdulgi authored Aug 16, 2024
2 parents 9e72552 + 48d4cdd commit 3b4623c
Show file tree
Hide file tree
Showing 22 changed files with 915 additions and 66 deletions.
7 changes: 4 additions & 3 deletions src/adminPage/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import LoginPage from "./pages/LoginPage.jsx";
import EventsPage from "./pages/EventsPage.jsx";
import EventsDetailPage from "./pages/EventsDetailPage.jsx";
import EventsCreatePage from "./pages/EventsCreatePage.jsx";
import EventsEditPage from "./pages/EventsEditPage.jsx";
import ProtectedRoute from "./pages/ProtectedRoute.jsx";
import RootRoute from "./pages/RootRoute.jsx";
import CommentsPage from "./pages/CommentsPage.jsx";
Expand All @@ -27,10 +28,10 @@ function App() {
<Route exact path="/events/create" element={<EventsCreatePage />} />
<Route
exact
path="/events/:id/edit"
element={<div>이벤트 수정하는 페이지</div>}
path="/events/:eventId/edit"
element={<EventsEditPage />}
/>
<Route path="/events/:id" element={<EventsDetailPage />} />
<Route path="/events/:eventId" element={<EventsDetailPage />} />
<Route path="/events" element={<EventsPage />} />
<Route path="/comments/:eventId" element={<CommentsIDPage />} />
<Route path="/comments" element={<CommentsPage />} />
Expand Down
22 changes: 0 additions & 22 deletions src/adminPage/features/eventEdit/Container.jsx

This file was deleted.

116 changes: 116 additions & 0 deletions src/adminPage/features/eventEdit/DateTimeRangeInput.jsx
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;
5 changes: 5 additions & 0 deletions src/adminPage/features/eventEdit/DrawInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function DrawInput() {
return <>추첨 이벤트임</>;
}

export default DrawInput;
98 changes: 98 additions & 0 deletions src/adminPage/features/eventEdit/EventBaseDataInput.jsx
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;
56 changes: 56 additions & 0 deletions src/adminPage/features/eventEdit/EventDetailInput.jsx
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;
12 changes: 12 additions & 0 deletions src/adminPage/features/eventEdit/EventEditFetcher.jsx
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;
5 changes: 5 additions & 0 deletions src/adminPage/features/eventEdit/FcfsInput.jsx
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 src/adminPage/features/eventEdit/businessLogic/DrawGradeData.js
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;
Loading

0 comments on commit 3b4623c

Please sign in to comment.