243 lines
6.0 KiB
TypeScript
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}`
|
|
}
|
|
}
|
|
})
|