Skip to content

Commit

Permalink
Merge pull request #10 from ii-sol/feature/4-mission
Browse files Browse the repository at this point in the history
feat: 미션 페이지 완료
  • Loading branch information
jiminpark23 authored Jun 18, 2024
2 parents 3e3aca0 + 56de62e commit b5baddd
Show file tree
Hide file tree
Showing 21 changed files with 604 additions and 35 deletions.
9 changes: 9 additions & 0 deletions src/assets/img/Mission/dishwashing.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/img/Mission/missionMain.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/img/Mission/missionRequest.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/img/common/complete.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions src/assets/img/common/receive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions src/assets/img/common/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
174 changes: 174 additions & 0 deletions src/components/Mission/DueDateBottomSheet.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React, { useState } from "react";
import { styled } from "styled-components";
import { setDueDate } from "../../store/reducers/Mission/mission";
import { BottomSheet } from "react-spring-bottom-sheet";
import "react-datepicker/dist/react-datepicker.css";
import DatePicker from "react-datepicker";
import ko from "date-fns/locale/ko";

const DueDateBottomSheet = ({ requestData, dispatch, open, onDismiss }) => {
const [selectedDate, setSelectedDate] = useState(null);
const [noDueDateSelected, setNoDueDateSelected] = useState(false);
const today = new Date();

const handleDateChange = (date) => {
setSelectedDate(date);
setNoDueDateSelected(false);
};

const handleSave = () => {
if (selectedDate && !noDueDateSelected) {
dispatch(setDueDate(selectedDate.toLocaleDateString()));
onDismiss();
} else if (noDueDateSelected) {
dispatch(setDueDate("완료일 없음"));
onDismiss();
} else {
dispatch(setDueDate(""));
onDismiss();
}
};

const handleNoDueDate = () => {
setSelectedDate("");
if (!noDueDateSelected) {
dispatch(setDueDate("완료일 없음"));
setNoDueDateSelected(true);
} else {
dispatch(setDueDate(""));
setNoDueDateSelected(false);
}
};

const datePickerProps = {
selected: selectedDate,
onChange: handleDateChange,
minDate: today,
dateFormat: "yyyy-MM-dd",
locale: ko,
renderCustomHeader: ({ date, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
<CustomHeader>
<button onClick={decreaseMonth} disabled={prevMonthButtonDisabled}>
{"<"}
</button>
<div>
{date.getFullYear()}{date.getMonth() + 1}
</div>
<button onClick={increaseMonth} disabled={nextMonthButtonDisabled}>
{">"}
</button>
</CustomHeader>
),
};

return (
<StyledBottomSheet open={open} onDismiss={onDismiss}>
<DatePickerWrapper>
<DatePicker {...datePickerProps} inline />
</DatePickerWrapper>

<NoDueDate $noDueDateSelected={noDueDateSelected} onClick={handleNoDueDate}>
✓ 완료일 없이 미션 요청하기
</NoDueDate>
<SaveButton onClick={handleSave}>확인</SaveButton>
</StyledBottomSheet>
);
};

export default DueDateBottomSheet;

const StyledBottomSheet = styled(BottomSheet)`
font-family: "Pretendard Variable";
& > div {
height: 55%;
padding: 20px;
box-sizing: border-box;
}
`;

const DatePickerWrapper = styled.div`
flex: 1;
display: flex;
justify-content: center;
margin-bottom: 50px;
width: 100%;
.react-datepicker {
font-size: 18px;
border: none;
}
.react-datepicker__header {
background-color: #ffffff;
}
.react-datepicker__current-month {
font-size: 20px;
margin-bottom: 10px;
}
.react-datepicker__day--selected {
background-color: #154b9b;
color: #fff;
}
.react-datepicker__day-name {
margin: 8px;
}
.react-datepicker__day-name:nth-child(1) {
color: red;
}
.react-datepicker__day-name:nth-child(7) {
color: blue;
}
.react-datepicker__day {
margin: 8px;
}
`;

const CustomHeader = styled.div`
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 20px;
& > button {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
}
`;

const SaveButton = styled.button`
position: absolute;
bottom: 20px;
right: 20px;
background-color: #e9f2ff;
color: #000;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
`;

const NoDueDate = styled.div`
position: absolute;
bottom: 20px;
left: 20px;
background-color: transparent;
color: ${(props) => (props.$noDueDateSelected ? "#154b9b" : "#97b2dd")};
border: none;
cursor: pointer;
font-size: 14px;
text-decoration: underline;
&:hover,
&:active {
color: ${(props) => (props.$noDueDateSelected ? "#154b9b" : "#97b2dd")};
-webkit-tap-highlight-color: transparent;
}
`;
64 changes: 64 additions & 0 deletions src/components/Mission/KeypadInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useEffect } from "react";
import tw from "twin.macro";
import { styled } from "styled-components";

import Keypad from "../common/Keypad";
import { normalizeNumber } from "../../utils/NormalizeNumber";

import CoinImage from "~/assets/img/Allowance/coin.svg";

const KeypadInput = ({ displayedNumber, setDisplayedNumber, initialPrice }) => {
useEffect(() => {
if (initialPrice !== 0) {
setDisplayedNumber(String(initialPrice));
}
}, [initialPrice, setDisplayedNumber]);

const handleNumberClick = (number) => {
if (displayedNumber.length < 7) {
setDisplayedNumber((prevNumber) => prevNumber + number);
}
};

const handleBackspace = () => {
if (displayedNumber.length > 1) {
setDisplayedNumber((prevNumber) => prevNumber.slice(0, -1));
} else {
setDisplayedNumber("0");
}
};

return (
<InputContainer>
<Img src={CoinImage} alt="코인" />
<Amount $displayedNumber={displayedNumber}>{normalizeNumber(displayedNumber)}</Amount>
<Keypad onNumberClick={handleNumberClick} onBackspace={handleBackspace} />
</InputContainer>
);
};

export default KeypadInput;

const InputContainer = styled.div`
${tw`flex flex-col gap-5 items-center`}
font-size: 18px;
`;

const Img = styled.img`
width: 143px;
height: auto;
margin-bottom: 16px;
`;

const Amount = styled.div`
width: ${(props) => (props.$displayedNumber && props.$displayedNumber.length > 0 ? "auto" : "123px")};
height: 49px;
background: #f5f5f5;
padding: 10px;
border-radius: 15px;
font-size: 18px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
`;
73 changes: 73 additions & 0 deletions src/components/Mission/MissionCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import tw from "twin.macro";
import { styled } from "styled-components";

import { normalizeNumber } from "../../utils/NormalizeNumber";

const MissionCard = ({ onClick, status, dday, mission, allowance, img }) => {
return (
<Container onClick={onClick}>
<Content>
{status && <StatusTag status={status}>{status}</StatusTag>}
{dday && <StatusTag dday={dday}>{parseInt(dday, 10) === 0 ? "D-day" : `D-${dday}`}</StatusTag>}
<Mission>{mission}</Mission>
<Allowance>{normalizeNumber(allowance)}</Allowance>
</Content>
<Img src={img} alt="아이콘" />
</Container>
);
};

export default MissionCard;

const Container = styled.div`
${tw`
flex
flex-col
p-5
gap-1
relative
`}
width: 148px;
height: 232px;
border-radius: 20px;
box-shadow: 0px 0px 15px 0px rgba(151, 178, 221, 0.4);
cursor: pointer;
`;

const Content = styled.div`
${tw`
flex
flex-col
items-start
gap-1
`}
`;

const StatusTag = styled.div`
font-size: 13px;
font-weight: 500;
padding: 4px 8px;
margin: 3px 0px;
border-radius: 5px;
color: ${({ status, dday }) => (status === "취소" || dday === "0" ? "#CC3535" : status || dday ? "#346BAC" : "#000000")};
background-color: ${({ status, dday }) => (status === "취소" || dday === "0" ? "#FFDCDC" : status || dday ? "#D5E0F1" : "#FFFFFF")};
`;

const Mission = styled.div`
font-weight: 700;
`;

const Allowance = styled.div`
color: #154b9b;
font-size: 15px;
font-weight: 700;
`;

const Img = styled.img`
position: absolute;
bottom: 12px;
right: 10px;
width: 78px;
height: auto;
`;
57 changes: 57 additions & 0 deletions src/components/Mission/MissionHistoryListItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import tw from "twin.macro";
import { styled } from "styled-components";
import * as S from "../../styles/GlobalStyles";

import MissionCard from "./MissionCard";

import DishwashingImg from "~/assets/img/Mission/dishwashing.svg";
import EmptyImage from "~/assets/img/common/empty.svg";

const MissionHistoryListItem = () => {
const data = [
{ id: 1, status: "완료", mission: "설거지 하기", allowance: 1000, img: DishwashingImg, createdDate: "2024-05-31" },
{ id: 2, status: "취소", mission: "설거지 하기", allowance: 1000, img: DishwashingImg, createdDate: "2024-06-04" },
{ id: 3, status: "취소", mission: "설거지 하기", allowance: 5000, img: DishwashingImg, createdDate: "2024-06-11" },
];

const renderItem = (item) => {
return <MissionCard key={item.id} status={item.status} mission={item.mission} allowance={item.allowance} img={item.img} />;
};

return (
<Container>
<List>
{data.length === 0 ? (
<EmptyState>
<Img src={EmptyImage} alt="No data" />
<EmptyText>미션 내역이 없어요</EmptyText>
</EmptyState>
) : (
<S.CardContainer>{data.map((item) => renderItem(item))}</S.CardContainer>
)}
</List>
</Container>
);
};

export default MissionHistoryListItem;

const Container = styled.div``;

const List = styled.ul`
${tw`list-none p-0`}
`;

const EmptyState = styled.div`
${tw`flex flex-col items-center justify-center h-full mt-20`}
`;

const Img = styled.img`
${tw`h-auto mb-4`}
width: 40%
`;

const EmptyText = styled.div`
${tw`text-2xl`}
`;
Loading

0 comments on commit b5baddd

Please sign in to comment.