Skip to content
Merged
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
24 changes: 17 additions & 7 deletions umc-master/src/apis/policyApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axiosInstance from '@apis/axios-instance';
//TODO: api ์—ฐ๊ฒฐ์‹œ ๊ตฌ์กฐ ๋ฐ”๋€” ์ˆ˜๋„ ์žˆ์Œ
import { PolicyData } from '@pages/magazine/components/cardGrid';
interface GetPoliciesParams {
locationId: number;
}
Expand All @@ -11,15 +11,13 @@ export interface Policy {
created_at: string;
updated_at: string;
policy_url: string;
magazine_image_url_list: {
image_name: string;
image_url: string;
}[];
image_url_list: [];
magazine_likes: number;
magazine_bookmarks: number;
organization: {
id: number;
name: string;
image: string;
};
location: {
id: number;
Expand All @@ -45,9 +43,15 @@ export interface PolicyMutationParams {
data: CreatePolicyParams;
}

export const getPolicies = async ({ locationId }: GetPoliciesParams): Promise<Policy[]> => {
export interface Hashtag {
hashtag_id: number;
name: string;
popularity: number;
}

export const getPolicies = async ({ locationId }: GetPoliciesParams): Promise<PolicyData[]> => {
const { data } = await axiosInstance.get(`/policies?location_id=${locationId}`);
return data.result;
return data.result.policy_list;
};

export const getPolicyGuide = async ({ policyId }: { policyId: number }): Promise<Policy> => {
Expand All @@ -69,3 +73,9 @@ export const deletePolicy = async ({ policyId }: { policyId: number }): Promise<
const response = await axiosInstance.delete(`/policies/${policyId}`);
return response.data.isSuccess;
};

// ์ธ๊ธฐ ๊ด€์‹ฌ์‚ฌ ์กฐํšŒ (๊ธฐ๋ณธ๊ฐ’ 6๊ฐœ)
export const getPopularHashtags = async ({ limit }: { limit: number }): Promise<Hashtag[]> => {
const response = await axiosInstance.get(`/hashtags/popular?limit=${limit}`);
return response.data.result;
};
9 changes: 8 additions & 1 deletion umc-master/src/apis/queries/usePolicyQueries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getPolicies, getPolicyGuide } from '@apis/policyApi';
import { getPolicies, getPolicyGuide, getPopularHashtags } from '@apis/policyApi';
import { keepPreviousData, useQuery } from '@tanstack/react-query';

interface PolicyListParams {
Expand All @@ -24,3 +24,10 @@ export const usePolicyGuide = ({ policyId }: PolicyGuideParams) => {
placeholderData: keepPreviousData,
});
};

export const usePopularHashtags = ({ limit }: { limit: number }) => {
return useQuery({
queryKey: ['hashtag'],
queryFn: () => getPopularHashtags({ limit }),
});
};
Binary file added umc-master/src/assets/character/magazine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions umc-master/src/components/Modal/image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import styled from 'styled-components';

interface ImageModalProps {
imageUrl: string;
onClose: () => void;
}

const ImageModal: React.FC<ImageModalProps> = ({ imageUrl, onClose }) => {
return (
<Overlay onClick={onClose}>
<ModalContent>
<Image src={imageUrl} alt="ํ™•๋Œ€๋œ ์ด๋ฏธ์ง€" />
</ModalContent>
</Overlay>
);
};

export default ImageModal;

const Overlay = styled.div`
position: fixed;
top: 30px;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
`;

const ModalContent = styled.div`
position: relative;
max-width: 70vh;
max-height: 70vh;
display: flex;
align-items: center;
justify-content: center;
`;

const Image = styled.img`
max-width: 100%;
max-height: 100%;
border-radius: 8px;
object-fit: contain;
`;
98 changes: 45 additions & 53 deletions umc-master/src/pages/magazine/MagazineDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,78 @@
import { useEffect } from 'react';
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import Typography from '@components/common/typography';
import Tag from '@components/Tag/Tag';

interface MagazineDetail {
id: string;
image: string;
title: string;
author: string;
date: string;
tags: string[];
description: string;
externalLink: string;
}
const dummyDetails: MagazineDetail[] = [
{
id: '1',
image: 'https://i.ibb.co/SXSyhmX6/image-11.png',
title: '์„œ๋ฆฌํ’€ ๋ณด๋””๊ฐ€๋“œ [์ฃผ๊ฑฐ์•ˆ์ „] - ํ™ˆ๋ฐฉ๋ฒ” ์‹œ์Šคํ…œ',
author: '์„œ์ดˆ1์ธ๊ฐ€๊ตฌ์ง€์›์„ผํ„ฐ',
date: '2024.12.30',
tags: ['๋ณด์•ˆ', '๋„์–ด๊ฐ€๋“œ', 'ํ™ˆ์ผ€์–ด'],
description: `ํ™ˆ ๋ฐฉ๋ฒ” ์‹œ์Šคํ…œ

- ๋„์–ด๊ฐ€๋“œ๋ฒจ, ๋ชฐ์นด์•ˆ์‹ฌ ์กด ๋“ฑ 1์ธ์„ธ ์„ค์น˜
- ์„ค์น˜๋น„: ๋ฌด๋ฃŒ
- ์—ฐ์ด์šฉ๋ฃŒ: 9,900์›
- ๋Œ€์ƒ: ์ธํ„ฐ๋„ท(์œ ์„ ) ์„ค์น˜๊ฐ€ ๋˜์–ด์žˆ๋Š” ์„œ์ดˆ๊ตฌ ๊ฑฐ์ฃผ 1์ธ๊ฐ€๊ตฌ
- ๋ฌด์ธ๊ฒฝ๋น„์„œ๋น„์Šค ์„ค์น˜ ์ฃผํƒ ์ ์šฉ ๋ถˆ๊ฐ€ (์‹ ๊ทœ ์•„ํŒŒํŠธ, ์‹ ๊ทœ ์˜คํ”ผ์Šคํ…”, ์ฒญ๋…„์ฃผํƒ ๋“ฑ)

๋ฌธ์˜: ์„œ์ดˆ1์ธ๊ฐ€๊ตฌ์ง€์›์„ผํ„ฐ`,
externalLink: 'https://example.com',
},
];
import { usePolicyGuide } from '@apis/queries/usePolicyQueries';
import ImageModal from '@components/Modal/image';

const MagazineDetailPage: React.FC = () => {
const { magazineId } = useParams<{ magazineId: string }>();
const { data, isLoading } = usePolicyGuide({ policyId: Number(magazineId) });
console.log('๋งค๊ฑฐ์ง„', data);

const formattedDescription = data?.description
.replace(/ {5,}/g, '\n\n') // ๊ณต๋ฐฑ 5์นธ ์ด์ƒ -> \n\n
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์ด๊ฑด ์ž๋™ ์ค„๋ฐ”๊ฟˆ์ธ ์…ˆ์ธ๊ฐ€์š”?

.replace(/ {3,4}/g, '\n'); // ๊ณต๋ฐฑ 3~4์นธ -> \n

const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedImage, setSelectedImage] = useState<string | null>(null);

useEffect(() => {
window.scrollTo(0, 0);
}, []);

// TODO: ์ถ”ํ›„ API ์—ฐ๋™ ์‹œ, ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๋„๋ก ์ˆ˜์ •
const detail = dummyDetails.find((item) => item.id === magazineId);
const handleImageClick = (imageUrl: string) => {
setSelectedImage(imageUrl);
setIsModalOpen(true);
};

if (!detail) {
return <Container>ํ•ด๋‹น ๋งค๊ฑฐ์ง„์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.</Container>;
if (isLoading) {
return;
}

return (
<Container>
<Image src={detail.image || '/placeholder.svg'} alt={detail.title} />
<Image
src={data?.image_url_list || '/placeholder.svg'}
alt={data?.title}
onClick={() => data?.image_url_list && handleImageClick(data?.image_url_list)}
/>
<Title>
<Typography variant="titleMedium">{detail.title}</Typography>
<Typography variant="titleMedium">{data?.title}</Typography>
</Title>
<AuthorContainer>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 20 }}>
<ProfileImage />
<ProfileImage
src={data?.organization.image || '/default-profile.png'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์‚ฌ์ง„์ด ์—†์„ ๋•Œ ๊ธฐ๋ณธ ์ด๋ฏธ์ง€๋กœ ๋ฐ”๋กœ ์ „๋‹ฌ๋˜๋‹ˆ ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์ด๋„ค์š”! if๋ฌธ์ด๋‚˜ && ํ˜•ํƒœ๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ๋ฐฐ์›Œ๊ฐ‘๋‹ˆ๋‹ค!

alt={data?.organization.name}
hasImage={!!data?.organization.image}
/>
<Author>
<Typography variant="titleXxSmall">{detail.author}</Typography>
<Typography variant="titleXxSmall">{data?.organization.name}</Typography>
</Author>
</div>
<Date>
<Typography variant="bodySmall">{detail.date}</Typography>
<Typography variant="bodySmall">{data?.updated_at.slice(0, 10)}</Typography>
</Date>
</AuthorContainer>
<Tags>
{detail.tags.map((tag) => (
<Tag key={tag} text={tag} selected />
))}
</Tags>
<Tags>{data?.hashtag?.map((tag) => <Tag key={tag.id} text={tag.name} selected />) ?? []}</Tags>
<Description>
<Typography variant="bodySmall">{detail.description}</Typography>
<Typography variant="bodySmall">{formattedDescription}</Typography>
</Description>
<Line />
<Button href={detail.externalLink} target="_blank" rel="noopener noreferrer">
<Button href={data?.policy_url} target="_blank" rel="noopener noreferrer">
<Typography variant="titleXSmall">ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ธฐ</Typography>
</Button>
{isModalOpen && selectedImage && <ImageModal imageUrl={selectedImage} onClose={() => setIsModalOpen(false)} />}
</Container>
);
};

export default MagazineDetailPage;

const Container = styled.div`
max-width: 1280px;
margin: 0 auto;
Expand All @@ -90,10 +81,11 @@ const Container = styled.div`

const Image = styled.img`
width: 100%;
height: 200px;
height: 400px;
object-fit: cover;
border-radius: 20px;
margin-bottom: 32px;
cursor: pointer;
`;

const Title = styled.div`
Expand All @@ -110,11 +102,13 @@ const AuthorContainer = styled.div`
margin-bottom: 32px;
`;

const ProfileImage = styled.div`
const ProfileImage = styled.img<{ hasImage: boolean }>`
width: 60px;
height: 60px;
border-radius: 50%;
background-color: ${({ theme }) => theme.colors.text.lightGray};
object-fit: cover;
background-color: ${({ hasImage, theme }) => (hasImage ? theme.colors.text.white : theme.colors.text.lightGray)};
box-shadow: ${({ hasImage }) => (hasImage ? '0px 4px 10px rgba(0, 0, 0, 0.15)' : 'none')};
`;

const Author = styled.div`
Expand Down Expand Up @@ -162,5 +156,3 @@ const Button = styled.a`
background-color: ${({ theme }) => theme.colors.primary[600]};
}
`;

export default MagazineDetailPage;
9 changes: 6 additions & 3 deletions umc-master/src/pages/magazine/MagazinePage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import { useEffect } from 'react';
import styled from 'styled-components';
import Typography from '@components/common/typography';
import MindMap from './components/mindMap';
import CardGrid, { CardGridData } from './components/cardGrid';
import dummyImg from '@assets/dummyImage/dummy.jpeg';
import { usePolicies } from '@apis/queries/usePolicyQueries';

const generateDummyData = (): CardGridData[] => {
return Array.from({ length: 9 }, (_, index) => ({
Expand All @@ -17,7 +20,7 @@ const generateDummyData = (): CardGridData[] => {
};

const MagazinePage = () => {
const programData = generateDummyData();
const { data: policiesData } = usePolicies({ locationId: 17 });
const influencerData = generateDummyData();

useEffect(() => {
Expand All @@ -31,9 +34,9 @@ const MagazinePage = () => {
</Title>
<MindMap />
<Title>
<Typography variant="headingXxSmall">์„œ์ดˆ๊ตฌ ์ง€์› ํ”„๋กœ๊ทธ๋žจ</Typography>
<Typography variant="headingXxSmall">์ข…๋กœ๊ตฌ ์ง€์› ํ”„๋กœ๊ทธ๋žจ</Typography>
</Title>
<CardGrid cards={programData} />
<CardGrid cards={policiesData || []} />
<Title>
<Typography variant="headingXxSmall">์ธํ”Œ๋ฃจ์–ธ์„œ ๊ฟ€ํŒ</Typography>
</Title>
Expand Down
30 changes: 27 additions & 3 deletions umc-master/src/pages/magazine/components/cardGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import React from 'react';
import styled from 'styled-components';
import { useNavigate } from 'react-router-dom';
import CardInfo from '@components/Card/CardInfo';
import dummyImg from '@assets/dummyImage/dummy.jpeg';

export interface PolicyData {
id: number;
title: string;
imageUrl: string;
likeCount: number;
bookmarkCount: number;
createAt: string;
}

export interface CardGridData {
id: string;
Expand All @@ -13,13 +23,25 @@ export interface CardGridData {
}

interface CardGridProps {
cards: CardGridData[];
cards: PolicyData[] | undefined;
}

interface ProcessedCardData extends CardGridData {
columnSpan: number;
}

const transformPolicies = (policies: PolicyData[] | undefined): CardGridData[] => {
if (!policies) return [];
return policies.map((policy) => ({
id: policy.id.toString(),
image: policy.imageUrl || dummyImg, // ์ด๋ฏธ์ง€ ์—†์„ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’
text: policy.title,
likes: policy.likeCount ?? 0, // undefined ๋ฐฉ์ง€
bookmarks: policy.bookmarkCount ?? 0, // undefined ๋ฐฉ์ง€
date: new Date(policy.createAt).toLocaleDateString('ko-KR'),
}));
};

const generatePattern = (): number[] => {
const patterns = [
[2, 1], // ํฐ ์นด๋“œ - ์ž‘์€ ์นด๋“œ
Expand All @@ -46,7 +68,9 @@ const applyPatternToCards = (cards: CardGridData[]): ProcessedCardData[] => {

const CardGrid: React.FC<CardGridProps> = ({ cards }) => {
const navigate = useNavigate();
const updatedCards = applyPatternToCards(cards);

const transformedCards = transformPolicies(cards);
const updatedCards = applyPatternToCards(transformedCards);

const handleClick = (id: string) => {
navigate(`/magazine/${id}`);
Expand Down Expand Up @@ -78,7 +102,7 @@ const GridContainer = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
align-items: start; // ์นด๋“œ๊ฐ€ ์œ„์ชฝ๋ถ€ํ„ฐ ์ •๋ ฌ๋˜๋„๋ก ์œ ์ง€
align-items: start;
`;

const GridItem = styled.div<{ columnSpan: number }>`
Expand Down
Loading