Files
landing-manager/frontend/app/stores/projects.ts
2026-03-05 10:35:28 +09:00

157 lines
3.9 KiB
TypeScript

import { defineStore } from 'pinia'
export type ProjectStatus = 'active' | 'archived' | 'draft'
export type ProjectSortBy = 'newest' | 'oldest' | 'most-leads' | 'most-variants'
export type ProjectStatusFilter = 'all' | ProjectStatus
export interface ProjectCard {
id: string
title: string
subtitle: string
status: ProjectStatus
leads: string
metricLabel: string
metricValue: string
trend: string
trendUp: boolean
variants: number
createdAt: string
}
const projectSource: ProjectCard[] = [
{
id: 'p1',
title: 'Q3 마케팅 캠페인',
subtitle: '2일 전 생성',
status: 'active',
leads: '1,240',
metricLabel: '전환율',
metricValue: '4.8%',
trend: '+12%',
trendUp: true,
variants: 3,
createdAt: '2026-03-01T09:00:00.000Z'
},
{
id: 'p2',
title: '블랙프라이데이 2023',
subtitle: '2023년 12월 1일 종료',
status: 'archived',
leads: '5,100',
metricLabel: '방문자',
metricValue: '42.5k',
trend: '종료',
trendUp: false,
variants: 3,
createdAt: '2023-12-01T03:00:00.000Z'
},
{
id: 'p3',
title: 'SaaS 웨비나 시리즈',
subtitle: '4시간 전 수정됨',
status: 'draft',
leads: '0',
metricLabel: '노출 수',
metricValue: '12k',
trend: '+18%',
trendUp: true,
variants: 2,
createdAt: '2026-03-04T01:20:00.000Z'
},
{
id: 'p4',
title: 'Product Hunt 런칭',
subtitle: '2주간 운영중',
status: 'active',
leads: '892',
metricLabel: '클릭률',
metricValue: '2.1%',
trend: '-2%',
trendUp: false,
variants: 1,
createdAt: '2026-02-12T16:30:00.000Z'
}
]
const toLeadNumber = (value: string) => {
const trimmed = value.trim().toLowerCase().replace(/,/g, '')
const numeric = Number(trimmed.replace('k', ''))
if (Number.isNaN(numeric)) {
return 0
}
return trimmed.endsWith('k') ? numeric * 1000 : numeric
}
export const useProjectsStore = defineStore('admin-projects', {
state: () => ({
projects: projectSource,
searchQuery: '',
statusFilter: 'all' as ProjectStatusFilter,
sortBy: 'newest' as ProjectSortBy
}),
getters: {
filteredProjects: (state): ProjectCard[] => {
const query = state.searchQuery.trim().toLowerCase()
const list = state.projects
.filter((project) => {
const target = `${project.title} ${project.subtitle}`.toLowerCase()
const matchQuery = query === '' || target.includes(query)
const matchStatus =
state.statusFilter === 'all' || project.status === state.statusFilter
return matchQuery && matchStatus
})
.sort((a, b) => {
if (state.sortBy === 'oldest') {
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
}
if (state.sortBy === 'most-leads') {
return toLeadNumber(b.leads) - toLeadNumber(a.leads)
}
if (state.sortBy === 'most-variants') {
return b.variants - a.variants
}
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
})
return list
}
},
actions: {
createProject(input: { name: string; campaignName: string }) {
const now = new Date().toISOString()
const id = `p-${Date.now().toString(36)}`
this.projects.unshift({
id,
title: input.name.trim(),
subtitle: `${input.campaignName.trim()}에서 생성`,
status: 'draft',
leads: '0',
metricLabel: '전환율',
metricValue: '-',
trend: '신규',
trendUp: true,
variants: 0,
createdAt: now
})
},
setSearchQuery(value: string) {
this.searchQuery = value
},
setStatusFilter(value: ProjectStatusFilter) {
this.statusFilter = value
},
setSortBy(value: ProjectSortBy) {
this.sortBy = value
}
}
})