Skip to content

Commit

Permalink
Merge branch 'main' into docs/update-readme
Browse files Browse the repository at this point in the history
  • Loading branch information
asn6878 committed Dec 3, 2024
2 parents 4ea21e0 + 0302197 commit dbc93fa
Show file tree
Hide file tree
Showing 23 changed files with 1,681 additions and 165 deletions.
899 changes: 891 additions & 8 deletions client/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.27.0",
"recharts": "^2.13.3",
"socket.io-client": "^4.8.1",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
Expand Down
20 changes: 20 additions & 0 deletions client/src/api/services/chart/chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { axiosInstance } from "@/api/instance";
import { ChartResponse, ChartPlatforms } from "@/types/chart";

export const chart = {
//금일 조회수
getToday: async (): Promise<ChartResponse> => {
const response = await axiosInstance.get<ChartResponse>("/api/statistic/today?limit=5");
return response.data;
},
//전체 조회수
getAll: async (): Promise<ChartResponse> => {
const response = await axiosInstance.get<ChartResponse>("/api/statistic/all?limit=5");
return response.data;
},
//금일 조회수
getPlatform: async (): Promise<ChartPlatforms> => {
const response = await axiosInstance.get<ChartPlatforms>("/api/statistic/platform");
return response.data;
},
};
28 changes: 23 additions & 5 deletions client/src/components/RssRegistration/RssRegistrationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useState } from "react";

import FormInput from "@/components/RssRegistration/FormInput";
import Alert from "@/components/common/Alert";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import {
Expand All @@ -15,6 +18,7 @@ import { useRegisterRss } from "@/hooks/queries/useRegisterRss";

import { validateRssUrl, validateName, validateEmail, validateBlogger } from "./RssValidation";
import { useRegisterModalStore } from "@/store/useRegisterModalStrore";
import { AlertType } from "@/types/alert";
import { RegisterRss } from "@/types/rss";

const Rss = [
Expand All @@ -33,6 +37,8 @@ const Rss = [
];

export default function RssRegistrationModal({ onClose, rssOpen }: { onClose: () => void; rssOpen: boolean }) {
const [alertOpen, setAlertOpen] = useState<AlertType>({ title: "", content: "", isOpen: false });

const {
rssUrl,
bloggerName,
Expand All @@ -52,17 +58,28 @@ export default function RssRegistrationModal({ onClose, rssOpen }: { onClose: ()
} = useRegisterModalStore();

const onSuccess = () => {
alert("RSS 등록이 성공적으로 완료되었습니다.");
resetInputs();
onClose();
setAlertOpen({
title: "RSS 요청 성공!",
content: "관리자가 검토후 처리 결과를 입력해주신 메일을 통해 전달드릴 예정이에요!",
isOpen: true,
});
};

const onError = (error: any) => {
alert(`RSS 등록에 실패했습니다: ${error.message}`);
const onError = () => {
setAlertOpen({
title: "RSS 요청 실패!",
content: "입력한 정보를 확인하거나 다시 시도해주세요. 문제가 계속되면 관리자에게 문의하세요!",
isOpen: true,
});
};

const { mutate } = useRegisterRss(onSuccess, onError);

const handleAlertClose = () => {
setAlertOpen({ title: "", content: "", isOpen: false });
resetInputs();
onClose();
};
const handleRegister = () => {
const data: RegisterRss = {
rssUrl: rssUrl,
Expand Down Expand Up @@ -133,6 +150,7 @@ export default function RssRegistrationModal({ onClose, rssOpen }: { onClose: ()
</Button>
</DialogFooter>
</DialogContent>
<Alert alertOpen={alertOpen} onClose={handleAlertClose} />
</Dialog>
);
}
Expand Down
14 changes: 10 additions & 4 deletions client/src/components/admin/layout/AdminTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from "react";

import { AxiosError } from "axios";
import { TriangleAlert } from "lucide-react";

import { RejectModal } from "@/components/admin/rss/RejectModal";
import AcceptedTab from "@/components/admin/taps/AcceptedTap";
Expand Down Expand Up @@ -84,10 +85,15 @@ export const AdminTabs = ({ setLogout }: { setLogout: () => void }) => {
if (pendingError || acceptedError || rejectedError)
return (
<div className="w-full h-full fixed top-0 left-0 bg-black bg-opacity-80">
<Alert className="w-[300px] h-[100px] fixed top-[50%] left-[50%] transform -translate-y-1/2 -translate-x-1/2">
<AlertTitle>세션이 만료되었습니다!</AlertTitle>
<AlertDescription>다시 로그인을 시도해 주세요.</AlertDescription>
<Button className="absolute right-1" onClick={setLogout}>
<Alert className="w-[25vw] h-[20vh] fixed top-[50%] left-[50%] transform -translate-y-1/2 -translate-x-1/2 flex flex-col justify-between">
<div className="flex gap-4 items-center pt-5">
<TriangleAlert />
<div className="flex flex-col gap-3">
<AlertTitle className="flex items-center gap-3">세션이 만료되었습니다!</AlertTitle>
<AlertDescription>서비스를 계속 사용하려면 로그인하세요.</AlertDescription>
</div>
</div>
<Button className="" onClick={setLogout}>
확인
</Button>
</Alert>
Expand Down
75 changes: 75 additions & 0 deletions client/src/components/chart/BarChartItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useState, useEffect, useRef } from "react";

import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";

import { ChartType } from "@/types/chart";

type BarType = {
data: ChartType[];
title: string;
description: string;
color: boolean;
};

export default function BarChartItem({ data, title, description, color }: BarType) {
const [componentWidth, setComponentWidth] = useState(0);
const cardRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
setComponentWidth(entry.contentRect.width);
}
});

if (cardRef.current) {
resizeObserver.observe(cardRef.current);
}
return () => {
if (cardRef.current) {
resizeObserver.unobserve(cardRef.current);
}
};
}, []);
const truncateText = (text: string) => {
const charWidth = 55;
const maxChars = Math.floor(componentWidth / charWidth);

return text.length > maxChars ? `${text.slice(0, Math.max(0, maxChars - 3))}...` : text;
};

const chartConfig = {
desktop: {
label: "Desktop",
color: color ? "hsl(200, 70%, 68%)" : "hsl(120, 70%, 68%)",
},
} satisfies ChartConfig;

return (
<Card ref={cardRef} className="w-[50%]">
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<BarChart accessibilityLayer data={data}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="title"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => truncateText(value)}
/>
<ChartTooltip cursor={true} content={<ChartTooltipContent />} />
<Bar dataKey="viewCount" fill="var(--color-desktop)" radius={8} />
</BarChart>
</ChartContainer>
</CardContent>
</Card>
);
}
24 changes: 24 additions & 0 deletions client/src/components/chart/Chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import BarChartItem from "@/components/chart/BarChartItem";
import ChartSkeleton from "@/components/chart/ChartSkeleton";
import PieChartItem from "@/components/chart/PieChartItem";

import { useChart } from "@/hooks/queries/useChart";

export default function Chart() {
const { data, isLoading, error } = useChart();
if (!data || isLoading) return <ChartSkeleton />;
if (error) return <p>Error loading data</p>;
const { chartAll, chartToday, chartPlatform } = data;

return (
<div className="p-8">
<div className="flex">
<BarChartItem title="전체 조회수" description="전체 조회수 TOP5" data={chartAll.data} color={true} />
<BarChartItem title="오늘의 조회수" description="금일 조회수 TOP5" data={chartToday.data} color={false} />
</div>
<div>
<PieChartItem data={chartPlatform.data} title="플랫폼별 블로그 수" />
</div>
</div>
);
}
16 changes: 16 additions & 0 deletions client/src/components/chart/ChartSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import BarChartItem from "@/components/chart/BarChartItem";
import PieChartItem from "@/components/chart/PieChartItem";

export default function ChartSkeleton() {
return (
<div className="p-8">
<div className="flex">
<BarChartItem title="전체 조회수" description="전체 조회수 TOP5" data={[]} color={true} />
<BarChartItem title="오늘의 조회수" description="금일 조회수 TOP5" data={[]} color={false} />
</div>
<div>
<PieChartItem data={[]} title="플랫폼별 블로그 수" />
</div>
</div>
);
}
55 changes: 55 additions & 0 deletions client/src/components/chart/PieChartItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { LabelList, Pie, PieChart, Cell } from "recharts";

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";

import { ChartPlatform } from "@/types/chart";

type BarType = {
data: ChartPlatform[];
title: string;
};

const chartConfig: ChartConfig = {
count: {
label: "Count",
},
tistory: {
label: "Tistory",
color: "hsl(210, 60%, 70%)",
},
velog: {
label: "Velog",
color: "hsl(150, 60%, 70%)",
},
etc: {
label: "etc",
color: "hsl(30, 60%, 70%)",
},
} satisfies ChartConfig;

export default function PieChartItem({ data, title }: BarType) {
return (
<Card className="flex flex-col">
<CardHeader className="items-center pb-0">
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent className="flex-1 pb-0">
<ChartContainer
config={chartConfig}
className="mx-auto aspect-square max-h-[250px] [&_.recharts-text]:fill-background"
>
<PieChart>
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
<Pie data={data} dataKey="count" nameKey="platform">
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={chartConfig[entry.platform]?.color || "#ccc"} />
))}
<LabelList dataKey="platform" className="fill-background" stroke="none" fontSize={12} />
</Pie>
</PieChart>
</ChartContainer>
</CardContent>
</Card>
);
}
2 changes: 1 addition & 1 deletion client/src/components/chat/ChatButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function OpenChat() {
return (
<button
onClick={toggleSidebar}
className="fixed text-white bottom-[18.5rem] right-7 bg-[#3498DB] hover:bg-[#2980B9] !rounded-full p-3"
className="fixed text-white bottom-[14.5rem] right-7 bg-[#3498DB] hover:bg-[#2980B9] !rounded-full p-3"
>
<MessageCircleMore size={25} />
</button>
Expand Down
27 changes: 27 additions & 0 deletions client/src/components/common/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";

import { AlertType } from "@/types/alert";

export default function Alert({ alertOpen, onClose }: { alertOpen: AlertType; onClose: () => void }) {
return (
<AlertDialog open={alertOpen.isOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{alertOpen.title}</AlertDialogTitle>
<AlertDialogDescription>{alertOpen.content}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction onClick={onClose}>확인</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
9 changes: 9 additions & 0 deletions client/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ function DesktopNavigation({ toggleModal }: { toggleModal: (modalType: "search"
<NavigationMenuItem>
<SideButton />
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
className={`${navigationMenuTriggerStyle()} hover:text-primary hover:bg-primary/10`}
onClick={() => toggleModal("login")}
href="#"
>
서비스 소개
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
className={`${navigationMenuTriggerStyle()} hover:text-primary hover:bg-primary/10`}
Expand Down
Loading

0 comments on commit dbc93fa

Please sign in to comment.