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

243 lines
6.0 KiB
TypeScript

import { defineStore } from 'pinia'
export type PageStatus = 'live' | 'draft' | 'paused'
export type PageSortBy =
| 'updated'
| 'name'
| 'leads'
| 'visitors'
| 'variants'
export interface CampaignLabel {
id: string
name: string
}
export interface AdminPage {
id: string
campaignId: string
name: string
domain: string
routePath: string
status: PageStatus
leadCount: number
visitorCount: number
variantCount: number
updatedAt: string
}
const campaigns: CampaignLabel[] = [
{ id: 'p1', name: 'Q3 마케팅 캠페인' },
{ id: 'p2', name: '블랙프라이데이 2023' },
{ id: 'p3', name: 'SaaS 웨비나 시리즈' },
{ id: 'p4', name: 'Product Hunt 론칭' }
]
const pageSource: AdminPage[] = [
{
id: 'lp01',
campaignId: 'p1',
name: '메인 랜딩',
domain: 'summer.ad-camp.kr',
routePath: '/',
status: 'live',
leadCount: 124,
visitorCount: 2400,
variantCount: 2,
updatedAt: '2026-03-04T09:12:00.000Z'
},
{
id: 'lp02',
campaignId: 'p1',
name: '오퍼 페이지 B',
domain: 'summer.ad-camp.kr',
routePath: '/offer',
status: 'draft',
leadCount: 16,
visitorCount: 420,
variantCount: 1,
updatedAt: '2026-03-03T18:05:00.000Z'
},
{
id: 'lp03',
campaignId: 'p2',
name: '구글 전용 랜딩',
domain: 'weekend.offer.kr',
routePath: '/google',
status: 'live',
leadCount: 88,
visitorCount: 1210,
variantCount: 3,
updatedAt: '2026-03-01T13:40:00.000Z'
},
{
id: 'lp04',
campaignId: 'p2',
name: '일반 버전',
domain: 'weekend.offer.kr',
routePath: '/',
status: 'paused',
leadCount: 22,
visitorCount: 540,
variantCount: 1,
updatedAt: '2026-02-28T06:25:00.000Z'
},
{
id: 'lp05',
campaignId: 'p4',
name: '리타겟 랜딩',
domain: 'retarget.example.com',
routePath: '/',
status: 'draft',
leadCount: 0,
visitorCount: 40,
variantCount: 1,
updatedAt: '2026-03-02T22:17:00.000Z'
}
]
const statusRank: Record<PageStatus, number> = {
live: 0,
draft: 1,
paused: 2
}
const statusLabel = (status: PageStatus) => {
if (status === 'live') {
return '라이브'
}
if (status === 'paused') {
return '일시정지'
}
return '초안'
}
export const usePagesStore = defineStore('admin-pages', {
state: () => ({
pages: pageSource,
campaigns,
searchQuery: '',
campaignFilter: 'all' as 'all' | string,
statusFilter: 'all' as 'all' | PageStatus,
sortBy: 'updated' as PageSortBy
}),
getters: {
filteredPages: (state): AdminPage[] => {
const query = state.searchQuery.trim().toLowerCase()
const result = state.pages
.filter((page) => {
const text = `${page.name} ${page.domain} ${page.routePath}`.toLowerCase()
const searchOk = query === '' || text.includes(query)
const campaignOk = state.campaignFilter === 'all' || page.campaignId === state.campaignFilter
const statusOk = state.statusFilter === 'all' || page.status === state.statusFilter
return searchOk && campaignOk && statusOk
})
.sort((a, b) => {
if (state.sortBy === 'name') {
return a.name.localeCompare(b.name)
}
if (state.sortBy === 'leads') {
return b.leadCount - a.leadCount
}
if (state.sortBy === 'visitors') {
return b.visitorCount - a.visitorCount
}
if (state.sortBy === 'variants') {
return b.variantCount - a.variantCount
}
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
})
return result
},
campaignOptions: (state): CampaignLabel[] => state.campaigns
},
actions: {
setSearchQuery(value: string) {
this.searchQuery = value
},
setCampaignFilter(value: 'all' | string) {
this.campaignFilter = value
},
setStatusFilter(value: 'all' | PageStatus) {
this.statusFilter = value
},
setSortBy(value: PageSortBy) {
this.sortBy = value
},
removePage(pageId: string) {
this.pages = this.pages.filter((page) => page.id !== pageId)
},
createQuickPage() {
const campaign = this.campaigns[this.pages.length % this.campaigns.length]
this.createPage({
name: `새 페이지 ${this.pages.length + 1}`,
campaignId: campaign.id,
domain: 'ad-camp-temp.test',
routePath: `/lp-${this.pages.length + 1}`
})
},
createPage(payload: {
name: string
campaignId: string
domain: string
routePath: string
}) {
const normalizedRoute = payload.routePath.trim().startsWith('/') ? payload.routePath.trim() : `/${payload.routePath.trim()}`
const normalizedDomain = payload.domain.trim()
const newPage: AdminPage = {
id: `lp-${Date.now()}`,
campaignId: payload.campaignId,
name: payload.name.trim(),
domain: normalizedDomain,
routePath: normalizedRoute,
status: 'draft',
leadCount: 0,
visitorCount: 0,
variantCount: 1,
updatedAt: new Date().toISOString()
}
this.pages.unshift(newPage)
},
statusClass(status: PageStatus) {
if (status === 'live') {
return {
label: statusLabel(status),
chipClass: 'text-emerald-200 bg-emerald-400/10',
dotClass: 'bg-emerald-300'
}
}
if (status === 'paused') {
return {
label: statusLabel(status),
chipClass: 'text-slate-300 bg-slate-500/10',
dotClass: 'bg-slate-400'
}
}
return {
label: statusLabel(status),
chipClass: 'text-amber-200 bg-amber-400/10',
dotClass: 'bg-amber-300'
}
},
campaignName(campaignId: string) {
return (
this.campaigns.find((campaign) => campaign.id === campaignId)?.name || campaignId || '미지정'
)
},
previewUrl(page: AdminPage) {
return `https://${page.domain}${page.routePath}`
}
}
})