Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/recipients.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const deleteRecipient = (id) => {
* @returns {Promise<object>} - 리액션 목록 정보
*/
export const getReactionsForRecipient = (id) => {
const endpoint = `/${team}/recipients/${id}/reactions/`;
const endpoint = `/recipients/${id}/reactions/`;
return fetchApi(endpoint);
};

Expand Down
84 changes: 58 additions & 26 deletions src/components/card-list/CardList.jsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,65 @@
import MessageCard from '../card/MessageCard';
import { CardlistContainer } from './CardList.styled';
import { CardlistContainer, LoadingContainer, NoMoreContent } from './CardList.styled';
import AddCard from '../card/CardAdd';

export default function CardList({ messages, isEditing, onDeleteMessage }) {
export default function CardList({
messages,
isEditing,
onDeleteMessage,
onCardClick,
onAddMessage,
loading,
hasMore
}) {
return (
<CardlistContainer>
<AddCard />
{messages.map(
({
id: messageId,
profileImageURL,
relationship,
sender,
content,
createdAt,
}) => (
<MessageCard
messageId={messageId}
key={messageId}
profileImage={profileImageURL}
name={sender}
status={relationship}
message={content}
date={createdAt}
isEditing={isEditing}
onDelete={onDeleteMessage}
/>
),
<>
<CardlistContainer>
<AddCard onClick={onAddMessage} />
{messages.map(
({
id: messageId,
profileImageURL,
relationship,
sender,
content,
createdAt,
...messageData
}) => (
<MessageCard
messageId={messageId}
key={messageId}
profileImage={profileImageURL}
name={sender}
status={relationship}
message={content}
date={createdAt}
isEditing={isEditing}
onDelete={onDeleteMessage}
onClick={() => onCardClick({
id: messageId,
profileImageURL,
relationship,
sender,
content,
createdAt,
...messageData
})}
/>
),
)}
</CardlistContainer>

{loading && (
<LoadingContainer>
<div>메시지를 불러오는 중...</div>
</LoadingContainer>
)}
</CardlistContainer>

{!hasMore && messages.length > 0 && (
<NoMoreContent>
<div>모든 메시지를 불러왔습니다.</div>
</NoMoreContent>
)}
</>
);
}
43 changes: 41 additions & 2 deletions src/components/card-list/CardList.styled.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,48 @@
import styled from 'styled-components';

export const CardlistContainer = styled.div`
max-width: 1200px;
margin: 100px auto;
padding: 0 24px;
display: flex;
justify-content: center;
justify-content: flex-start;
flex-wrap: wrap;
gap: 28px 24px;
margin: 100px auto;

@media (max-width: 1248px) {
padding: 0 24px;

/* 카드 너비 반응형 조정 */
& > * {
flex: 0 0 calc(50% - 12px);
max-width: calc(50% - 12px);
}
}

@media (max-width: 768px) {
gap: 20px 16px;

& > * {
flex: 0 0 100%;
max-width: 100%;
}
}
`;

export const LoadingContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 40px 20px;
color: #666;
font-size: 16px;
`;

export const NoMoreContent = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 40px 20px;
color: #999;
font-size: 14px;
`;
42 changes: 38 additions & 4 deletions src/components/card/CardAdd.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,62 @@ const CardContainer = styled.div`
width: 384px;
height: 280px;
padding: 40px;
/* background: linear-gradient(135deg, #f0f0f0 0%, #e8e8e8 100%); */
display: flex;
justify-content: center;
align-items: center;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
cursor: pointer;
transition: all 0.2s ease;
background-color: #f8f9fa;
border: 2px dashed #ddd;

&:hover {
transform: translateY(-4px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
border-color: #9935ff;
background-color: #f5f0ff;
}

@media (max-width: 1248px) {
width: 100%;
max-width: 100%;
}

@media (max-width: 768px) {
height: auto;
min-height: 280px;
padding: 24px;
}
`;

const ImageBox = styled.div`
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;

img {
width: 100%;
height: 100%;
/* object-fit: cover; */
opacity: 0.7;
transition: opacity 0.2s ease;
}

${CardContainer}:hover & img {
opacity: 1;
}

@media (max-width: 768px) {
width: 48px;
height: 48px;
}
`;

const AddCard = () => {
const AddCard = ({ onClick }) => {
return (
<CardContainer>
<CardContainer onClick={onClick}>
<ImageBox>
<img src={plusImg} alt="plus icon" />
</ImageBox>
Expand Down
25 changes: 22 additions & 3 deletions src/components/card/MessageCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,25 @@ const MessageCard = ({
date = '2025.07.12',
isEditing,
onDelete,
onClick,
}) => {
const formatDate = (dateString) => {
const dateObj = new Date(dateString);
return dateObj.toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).replace(/\. /g, '.').replace(/\.$/, '');
};

const handleCardClick = () => {
if (!isEditing && onClick) {
onClick();
}
};

return (
<CardContainer>
<CardContainer onClick={handleCardClick} isClickable={!isEditing}>
<Header>
<ProfileImage>
{profileImage ? (
Expand All @@ -44,7 +60,10 @@ const MessageCard = ({
<StatusBadge>{status}</StatusBadge>
</HeaderInfo>
{isEditing && (
<Button onClick={() => onDelete(messageId)} variant="icon">
<Button onClick={(e) => {
e.stopPropagation();
onDelete(messageId);
}} variant="icon">
🗑️
</Button>
)}
Expand All @@ -54,7 +73,7 @@ const MessageCard = ({
<MessageText>{message}</MessageText>
</MessageContent>

<DateText>{date}</DateText>
<DateText>{formatDate(date)}</DateText>
</CardContainer>
);
};
Expand Down
40 changes: 37 additions & 3 deletions src/components/card/MessageCard.styled.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,28 @@ export const CardContainer = styled.div`
width: 384px;
height: 280px;
padding: 40px;
/* background: linear-gradient(135deg, #f0f0f0 0%, #e8e8e8 100%); */
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
cursor: ${props => props.isClickable ? 'pointer' : 'default'};
transition: all 0.2s ease;

${props => props.isClickable && `
&:hover {
transform: translateY(-4px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
}
`}

@media (max-width: 1248px) {
width: 100%;
max-width: 100%;
}

@media (max-width: 768px) {
height: auto;
min-height: 280px;
padding: 24px;
}
`;

export const Header = styled.div`
Expand All @@ -27,11 +46,18 @@ export const ProfileImage = styled.div`
align-items: center;
justify-content: center;
overflow: hidden;
flex-shrink: 0;

img {
width: 100%;
height: 100%;
/* object-fit: cover; */
object-fit: cover;
}

@media (max-width: 768px) {
width: 48px;
height: 48px;
margin-right: 12px;
}
`;

Expand Down Expand Up @@ -72,12 +98,20 @@ export const MessageContent = styled.div`
export const MessageText = styled.p`
width: 100%;
height: 106px;
overflow-y: auto; /* 내용이 넘치면 세로 스크롤 */
overflow-y: auto;
overflow-x: hidden;
font-size: 18px;
line-height: 28px;
color: #4a4a4a;
font-weight: 400;
margin: 0;

@media (max-width: 768px) {
font-size: 16px;
line-height: 24px;
height: auto;
min-height: 80px;
}
`;

export const DateText = styled.div`
Expand Down
Loading