Skip to main content
Glama
WholesaleOrderManagement.tsx19.9 kB
import React from 'react'; // Design System 컴포넌트 import import { Button } from '@dealicious/design-system-react/src/components/ssm-button'; import { Chip } from '@dealicious/design-system-react/src/components/ssm-chip'; import { Tag } from '@dealicious/design-system-react/src/components/ssm-tag'; import { Text } from '@dealicious/design-system-react/src/components/ssm-text'; import { Icon } from '@dealicious/design-system-react/src/components/ssm-icon'; // 이미지 상수 정의 (Figma MCP에서 가져온 assets) const imgComThumb80X80 = "http://localhost:3845/assets/a23a50ac4b0c21068425b9fbad7b10871c10708b.png"; const imgArrowRight = "http://localhost:3845/assets/f6843e9d48c5f4efce619a1dd1a4ac585682c746.svg"; const imgLogoSymbol = "http://localhost:3845/assets/cfdcad20a987a42a3a82975b10558672c6989679.svg"; const imgPlusText = "http://localhost:3845/assets/02fb25fdf55c3329cb12788dda06eb81c93a6c97.svg"; const imgNewBadge = "http://localhost:3845/assets/a996cd8d306819476ae2b46cf48c5b05582487a0.svg"; const imgTalkIcon = "http://localhost:3845/assets/9fc03fee616256d57ccf3c58c9348e5a2995aeb2.svg"; const imgSmallArrow = "http://localhost:3845/assets/f9c30996c34ac82743f76638e5ffa7124a4fcac3.svg"; // 디자인 토큰 (Figma에서 추출) const designTokens = { colors: { textG100: '#222222', textG80: '#686E7B', textG70: '#8F97A7', lineG100: '#222222', lineG20: '#EBEEF6', lineG30: '#DFE3ED', bgG10: '#F5F6FB', bgG100: '#222222', primary01: '#FB4760', primary03: '#FEECEF', primary04: '#FFFFFF', secondary01: '#4759FB', secondary03: '#EAEFFF', mbs01: '#1F5EFF', mbs02: '#E5FE1E', }, }; /** * 주문 데이터 인터페이스 */ interface Order { id: string; status: 'new' | 'complete_request' | 'packaged' | 'completed' | 'delivery'; deliveryType: string; paymentType: string; storeName: string; productName: string; price: number; thumbnailUrl: string; isPlusStore?: boolean; isNew?: boolean; hasActionButton?: boolean; } /** * 필터 칩 데이터 인터페이스 */ interface FilterChip { id: string; label: string; count?: number; isSelected: boolean; } /** * 도매 매장 주문 관리 컴포넌트 Props */ interface WholesaleOrderManagementProps { orders?: Order[]; filterChips?: FilterChip[]; onFilterChange?: (chipId: string) => void; onOrderClick?: (orderId: string) => void; onStatusChange?: (orderId: string) => void; } /** * Plus 배지 컴포넌트 * 신상마켓 플러스 매장 표시 배지 */ const PlusBadge: React.FC = () => ( <div style={{ backgroundColor: designTokens.colors.mbs01, height: '10px', borderRadius: '33px', width: '26px', display: 'flex', alignItems: 'center', padding: '0 3px', }}> <div style={{ width: '4.5px', height: '4.5px' }}> <img alt="로고" src={imgLogoSymbol} style={{ width: '100%', height: '100%' }} /> </div> <div style={{ width: '14px', height: '4.5px', marginLeft: '1.5px' }}> <img alt="plus" src={imgPlusText} style={{ width: '100%', height: '100%' }} /> </div> </div> ); /** * NEW 배지 컴포넌트 */ const NewBadge: React.FC = () => ( <div style={{ backgroundColor: designTokens.colors.primary01, borderRadius: '11px', width: '10px', height: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center', }}> <img alt="N" src={imgNewBadge} style={{ width: '4px', height: '4px' }} /> </div> ); /** * 주문서 보기 링크 컴포넌트 */ const OrderViewLink: React.FC<{ onClick?: () => void }> = ({ onClick }) => ( <div style={{ display: 'flex', gap: '2.5px', alignItems: 'center', padding: '1.3px 0', cursor: 'pointer', }} onClick={onClick} > <Text variant="body" size="small" style={{ color: designTokens.colors.textG80, fontSize: '8.15px' }}> 주문서 보기 </Text> <div style={{ width: '16px', height: '16px' }}> <img alt="화살표" src={imgArrowRight} style={{ width: '100%', height: '100%' }} /> </div> </div> ); /** * 톡 아이콘 버튼 컴포넌트 */ const TalkButton: React.FC = () => ( <div style={{ display: 'flex', gap: '2.5px', alignItems: 'center', padding: '0 10px', height: '20px', cursor: 'pointer', }}> <div style={{ width: '6.7px', height: '6.7px', backgroundColor: designTokens.colors.textG70, borderRadius: '1px', overflow: 'hidden', }}> <img alt="톡" src={imgTalkIcon} style={{ width: '100%', height: '100%' }} /> </div> <div style={{ width: '6.7px', height: '6.7px' }}> <img alt="화살표" src={imgSmallArrow} style={{ width: '100%', height: '100%' }} /> </div> </div> ); /** * 상태에 따른 태그 스타일 반환 */ const getStatusTagStyle = (status: Order['status']): { bgColor: string; textColor: string; label: string } => { switch (status) { case 'new': return { bgColor: designTokens.colors.secondary03, textColor: designTokens.colors.secondary01, label: '신규주문', }; case 'complete_request': return { bgColor: designTokens.colors.primary03, textColor: designTokens.colors.primary01, label: '거래완료 요청', }; case 'packaged': return { bgColor: designTokens.colors.secondary03, textColor: designTokens.colors.secondary01, label: '포장완료', }; case 'completed': return { bgColor: '#E8F5E9', textColor: '#4CAF50', label: '거래완료', }; case 'delivery': return { bgColor: '#FFF3E0', textColor: '#FF9800', label: '택배주문', }; default: return { bgColor: designTokens.colors.lineG20, textColor: designTokens.colors.textG80, label: '알 수 없음', }; } }; /** * 주문 항목 컴포넌트 */ const OrderItem: React.FC<{ order: Order; onOrderClick?: () => void; onStatusChange?: () => void; }> = ({ order, onOrderClick, onStatusChange }) => { const statusStyle = getStatusTagStyle(order.status); return ( <div style={{ backgroundColor: designTokens.colors.primary04, display: 'flex', flexDirection: 'column', gap: '10px', alignItems: 'flex-start', padding: '10px', width: '100%', }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '7.5px', alignItems: 'flex-start', width: '100%', }}> {/* 태그 및 주문서 보기 링크 */} <div style={{ display: 'flex', gap: '5px', alignItems: 'center', width: '100%', }}> {/* 상태 태그 그룹 */} <div style={{ flex: 1, display: 'flex', gap: '2.5px', alignItems: 'center' }}> {/* 주문 상태 태그 */} <Tag variant="custom" size="small" style={{ backgroundColor: statusStyle.bgColor, color: statusStyle.textColor, height: '16px', padding: '0 5px', borderRadius: '4px', fontSize: '7.5px', fontWeight: 600, }} > {statusStyle.label} </Tag> {/* 배송 유형 태그 */} <Tag variant="outlined" size="small" style={{ backgroundColor: designTokens.colors.primary04, border: `1px solid ${designTokens.colors.lineG20}`, color: designTokens.colors.textG80, height: '16px', padding: '0 5px', borderRadius: '4px', fontSize: '7.5px', fontWeight: 600, }} > {order.deliveryType} </Tag> {/* 결제 유형 태그 */} <Tag variant="outlined" size="small" style={{ backgroundColor: designTokens.colors.primary04, border: `1px solid ${designTokens.colors.lineG20}`, color: designTokens.colors.textG80, height: '16px', padding: '0 5px', borderRadius: '4px', fontSize: '7.5px', fontWeight: 600, }} > {order.paymentType} </Tag> </div> {/* 주문서 보기 링크 */} <OrderViewLink onClick={onOrderClick} /> </div> {/* 주문 정보 */} <div style={{ display: 'flex', gap: '7.5px', alignItems: 'center', width: '100%', }}> {/* 상품 썸네일 */} <div style={{ border: '0.3px solid rgba(0,0,0,0.05)', borderRadius: '6px', width: '45px', height: '45px', overflow: 'hidden', }}> <img alt="상품 이미지" src={order.thumbnailUrl} style={{ width: '100%', height: '100%', objectFit: 'cover', }} /> </div> {/* 상품 상세 정보 */} <div style={{ display: 'flex', flexDirection: 'column', gap: '5px', flex: 1, justifyContent: 'center', }}> {/* 매장명 및 배지 */} <div style={{ display: 'flex', flexDirection: 'column', gap: '1.3px', }}> <div style={{ display: 'flex', gap: '2.5px', alignItems: 'center', }}> <Text variant="heading" size="small" style={{ color: designTokens.colors.textG100, fontSize: '9.4px', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', }} > {order.storeName} </Text> {order.isPlusStore && <PlusBadge />} {order.isNew && <NewBadge />} </div> <Text variant="body" size="small" style={{ color: designTokens.colors.textG80, fontSize: '8.77px', }} > {order.productName} </Text> </div> {/* 가격 정보 */} <div style={{ display: 'flex', alignItems: 'center', height: '12.5px', position: 'relative', }}> <Text variant="heading" size="small" style={{ color: designTokens.colors.textG100, fontSize: '8.77px', fontWeight: 600, flex: 1, }} > ₩{order.price.toLocaleString()} </Text> {/* 톡 버튼 */} <div style={{ position: 'absolute', right: '-10px', top: '-3.8px' }}> <TalkButton /> </div> </div> </div> </div> </div> {/* 액션 버튼 (신규주문인 경우만 표시) */} {order.hasActionButton && ( <Button variant="secondary" size="medium" onClick={onStatusChange} style={{ width: '100%', minHeight: '29px', padding: '8px 12.5px', border: `1px solid ${designTokens.colors.lineG30}`, borderRadius: '6px', backgroundColor: designTokens.colors.primary04, color: designTokens.colors.textG80, fontSize: '8.77px', fontWeight: 600, }} > 포장 완료 상태로 변경하기 </Button> )} </div> ); }; /** * 도매 매장 주문 관리 컴포넌트 * Figma 디자인(node: 45733:32370)을 Design System 컴포넌트로 변환 */ export default function WholesaleOrderManagement({ orders: propOrders, filterChips: propFilterChips, onFilterChange, onOrderClick, onStatusChange, }: WholesaleOrderManagementProps) { // 기본 필터 칩 데이터 const defaultFilterChips: FilterChip[] = [ { id: 'all', label: '전체', isSelected: true }, { id: 'new', label: '주문', count: 3, isSelected: false }, { id: 'packaged', label: '포장완료', isSelected: false }, { id: 'completed', label: '거래완료', isSelected: false }, { id: 'delivery', label: '택배주문', isSelected: false }, ]; // 기본 주문 데이터 const defaultOrders: Order[] = [ { id: '1', status: 'new', deliveryType: '사입사 방문', paymentType: '현장 결제', storeName: '신상플래닛', productName: '베스트 검정 니트', price: 30000, thumbnailUrl: imgComThumb80X80, isPlusStore: true, isNew: true, hasActionButton: true, }, { id: '2', status: 'complete_request', deliveryType: '직접 수령', paymentType: '현장 결제', storeName: '신상랩', productName: '데일리 티셔츠 외 2건', price: 56000, thumbnailUrl: imgComThumb80X80, isPlusStore: true, isNew: false, hasActionButton: false, }, ]; const [selectedFilter, setSelectedFilter] = React.useState('all'); const filterChips = propFilterChips || defaultFilterChips; const orders = propOrders || defaultOrders; // 필터 변경 핸들러 const handleFilterChange = (chipId: string) => { setSelectedFilter(chipId); onFilterChange?.(chipId); }; // 신규주문 개수 계산 const newOrderCount = orders.filter(order => order.status === 'new').length; return ( <div style={{ backgroundColor: designTokens.colors.bgG10, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', padding: '16px 12px', borderRadius: '6px', width: '100%', }}> <div style={{ backgroundColor: designTokens.colors.primary04, display: 'flex', flexDirection: 'column', alignItems: 'center', overflow: 'hidden', borderRadius: '6px', width: '100%', }}> {/* 상단 필터 바 */} <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%', }}> {/* 필터 칩 영역 */} <div style={{ backgroundColor: designTokens.colors.primary04, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', overflowX: 'auto', overflowY: 'hidden', width: '100%', }}> <div style={{ display: 'flex', gap: '2.5px', alignItems: 'center', padding: '7.5px 10px', width: '100%', }}> <div style={{ display: 'flex', gap: '2.5px', alignItems: 'center', flex: 1, }}> {filterChips.map((chip) => { const isSelected = selectedFilter === chip.id; return ( <Chip key={chip.id} variant={isSelected ? 'filled' : 'outlined'} size="small" onClick={() => handleFilterChange(chip.id)} style={{ height: '22px', padding: '6.9px 8.25px', borderRadius: '100px', backgroundColor: designTokens.colors.primary04, border: isSelected ? `1px solid ${designTokens.colors.lineG100}` : `1px solid ${designTokens.colors.lineG20}`, color: designTokens.colors.textG100, fontSize: '9.63px', fontWeight: isSelected ? 600 : 400, cursor: 'pointer', }} > {chip.label}{chip.count ? ` ${chip.count}` : ''} </Chip> ); })} </div> </div> </div> {/* 구분선 */} <div style={{ backgroundColor: designTokens.colors.lineG20, height: '0.63px', width: '100%', }} /> {/* 신규주문 카운트 */} <div style={{ backgroundColor: designTokens.colors.primary04, display: 'flex', gap: '5px', alignItems: 'flex-start', padding: '8.8px 9.4px', height: '30px', width: '100%', }}> <div style={{ display: 'flex', gap: '1.3px', alignItems: 'center', flex: 1, }}> <Text variant="body" size="small" style={{ color: designTokens.colors.textG100, fontSize: '8.77px', }} > 신규주문 </Text> <Text variant="heading" size="small" style={{ color: designTokens.colors.textG100, fontSize: '8.77px', fontWeight: 600, }} > {newOrderCount} </Text> </div> </div> </div> {/* 주문 목록 */} <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: '100%', }}> {/* 상단 구분선 */} <div style={{ backgroundColor: designTokens.colors.lineG20, height: '0.63px', width: '100%', }} /> {/* 주문 항목 렌더링 */} {orders.map((order, index) => ( <React.Fragment key={order.id}> <OrderItem order={order} onOrderClick={() => onOrderClick?.(order.id)} onStatusChange={() => onStatusChange?.(order.id)} /> {/* 주문 항목 간 구분선 */} {index < orders.length - 1 && ( <div style={{ backgroundColor: designTokens.colors.lineG20, height: '0.63px', width: '100%', }} /> )} </React.Fragment> ))} </div> {/* 홈 인디케이터 (모바일 UI) */} <div style={{ backgroundColor: designTokens.colors.primary04, height: '21.3px', width: '100%', display: 'flex', alignItems: 'flex-end', justifyContent: 'center', paddingBottom: '5.6px', }}> <div style={{ backgroundColor: designTokens.colors.bgG100, height: '3.1px', width: '84px', borderRadius: '100px', }} /> </div> </div> </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Opti-kjh/palatte'

If you have feedback or need assistance with the MCP directory API, please join our Discord server