Add media outlet pages with articles and related auction details
Integrates new API endpoints for fetching articles and auction data for each media outlet, displaying them on dedicated pages with article listings and auction information. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 069d4324-6c40-4355-955e-c714a50de1ea Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/069d4324-6c40-4355-955e-c714a50de1ea/jvFIdY3
This commit is contained in:
@ -1,14 +1,16 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useRoute } from "wouter";
|
||||
import { useRoute, useLocation } from "wouter";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Gavel, Clock, TrendingUp } from "lucide-react";
|
||||
import ArticleCard from "@/components/ArticleCard";
|
||||
import type { MediaOutlet, Article } from "@shared/schema";
|
||||
import type { MediaOutlet, Article, Auction } from "@shared/schema";
|
||||
|
||||
export default function MediaOutlet() {
|
||||
const [, params] = useRoute("/media/:slug");
|
||||
const [, setLocation] = useLocation();
|
||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
||||
|
||||
const { data: outlet, isLoading: outletLoading } = useQuery<MediaOutlet>({
|
||||
@ -17,10 +19,38 @@ export default function MediaOutlet() {
|
||||
});
|
||||
|
||||
const { data: articles = [], isLoading: articlesLoading } = useQuery<Article[]>({
|
||||
queryKey: ["/api/media-outlets", outlet?.id, "articles"],
|
||||
enabled: !!outlet?.id
|
||||
queryKey: ["/api/media-outlets", params?.slug, "articles"],
|
||||
enabled: !!params?.slug
|
||||
});
|
||||
|
||||
const { data: auction, isLoading: auctionLoading } = useQuery<Auction>({
|
||||
queryKey: ["/api/media-outlets", params?.slug, "auction"],
|
||||
enabled: !!params?.slug
|
||||
});
|
||||
|
||||
const formatCurrency = (amount: string | null) => {
|
||||
if (!amount) return "₩0";
|
||||
return new Intl.NumberFormat('ko-KR', {
|
||||
style: 'currency',
|
||||
currency: 'KRW'
|
||||
}).format(parseFloat(amount));
|
||||
};
|
||||
|
||||
const formatTimeRemaining = (endDate: Date | string) => {
|
||||
const end = new Date(endDate);
|
||||
const now = new Date();
|
||||
const diff = end.getTime() - now.getTime();
|
||||
|
||||
if (diff <= 0) return "경매 종료";
|
||||
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
|
||||
if (days > 0) return `${days}일 ${hours}시간 남음`;
|
||||
if (hours > 0) return `${hours}시간 남음`;
|
||||
return "1시간 미만 남음";
|
||||
};
|
||||
|
||||
if (outletLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@ -97,7 +127,7 @@ export default function MediaOutlet() {
|
||||
<div className="flex-1">
|
||||
<h1 className="text-3xl font-bold mb-2">{outlet.name}</h1>
|
||||
<p className="text-lg text-muted-foreground mb-4">{outlet.description}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-2 mb-4">
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
{outlet.category}
|
||||
</Badge>
|
||||
@ -107,6 +137,68 @@ export default function MediaOutlet() {
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Auction Section */}
|
||||
{!auctionLoading && auction ? (
|
||||
<Card className="border-orange-200 bg-orange-50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Gavel className="h-5 w-5 text-orange-600" />
|
||||
<span className="font-semibold text-orange-900">관리자 권한 경매</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-sm text-orange-700">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
<span>현재 최고가: {formatCurrency(auction.currentBid)}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-sm text-orange-700">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span>{formatTimeRemaining(auction.endDate)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setLocation(`/media/${params?.slug}/auction`)}
|
||||
size="sm"
|
||||
className="bg-orange-600 hover:bg-orange-700"
|
||||
data-testid="button-auction"
|
||||
>
|
||||
입찰하기
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : !auctionLoading && !auction ? (
|
||||
<Card className="border-gray-200 bg-gray-50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Gavel className="h-5 w-5 text-gray-500" />
|
||||
<span className="text-gray-700">현재 진행 중인 경매가 없습니다</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled
|
||||
data-testid="button-no-auction"
|
||||
>
|
||||
경매 대기 중
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="border-gray-200 bg-gray-50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="animate-pulse flex space-x-4">
|
||||
<div className="rounded-full bg-gray-300 h-5 w-5"></div>
|
||||
<div className="h-4 bg-gray-300 rounded w-24"></div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user