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

239 lines
5.5 KiB
TypeScript

import { defineStore } from 'pinia'
export interface PageVariant {
id: string
pageId: string
name: string
description: string
trafficWeight: number
isActive: boolean
leadCount: number
updatedAt: string
}
export interface CreateVariantInput {
pageId: string
name: string
description: string
trafficWeight: number
}
export interface UpdateVariantInput {
id: string
pageId: string
name?: string
description?: string
trafficWeight?: number
isActive?: boolean
leadCount?: number
}
type VariantMap = Record<string, PageVariant[]>
const toVariantIso = (value = 0) => {
const date = new Date()
date.setSeconds(date.getSeconds() + value)
return date.toISOString()
}
const variantSeed: VariantMap = {
lp01: [
{
id: 'v-lp01-a',
pageId: 'lp01',
name: '일반형',
description: '현재 운영 중인 기본 랜딩 버전',
trafficWeight: 70,
isActive: true,
leadCount: 76,
updatedAt: toVariantIso(-1200)
},
{
id: 'v-lp01-b',
pageId: 'lp01',
name: '강한 메시지형',
description: 'CTA 문구를 자극적으로 강화한 변형',
trafficWeight: 30,
isActive: true,
leadCount: 48,
updatedAt: toVariantIso(-900)
}
],
lp02: [
{
id: 'v-lp02-a',
pageId: 'lp02',
name: '기본 오퍼',
description: '초안 상태 테스트용 템플릿',
trafficWeight: 100,
isActive: true,
leadCount: 16,
updatedAt: toVariantIso(-2000)
}
],
lp03: [
{
id: 'v-lp03-a',
pageId: 'lp03',
name: '구글 버전 A',
description: '광고 채널별로 분기되는 기본 버전',
trafficWeight: 50,
isActive: true,
leadCount: 58,
updatedAt: toVariantIso(-1300)
},
{
id: 'v-lp03-b',
pageId: 'lp03',
name: '구글 버전 B',
description: '강조 문구/이미지를 변경한 실험군',
trafficWeight: 30,
isActive: true,
leadCount: 22,
updatedAt: toVariantIso(-800)
},
{
id: 'v-lp03-c',
pageId: 'lp03',
name: '구글 버전 C',
description: '폼 위치를 상단으로 이동한 변형',
trafficWeight: 20,
isActive: true,
leadCount: 8,
updatedAt: toVariantIso(-500)
}
],
lp04: [
{
id: 'v-lp04-a',
pageId: 'lp04',
name: '저녁 타겟형',
description: '기본 버전',
trafficWeight: 100,
isActive: false,
leadCount: 22,
updatedAt: toVariantIso(-700)
}
],
lp05: [
{
id: 'v-lp05-a',
pageId: 'lp05',
name: '리타겟 기본형',
description: '리마케팅 유입용 기본 템플릿',
trafficWeight: 100,
isActive: true,
leadCount: 0,
updatedAt: toVariantIso(-1000)
}
]
}
const clampTrafficWeight = (value: number) => {
if (!Number.isFinite(value)) {
return 1
}
if (value < 1) {
return 1
}
if (value > 100) {
return 100
}
return Math.round(value)
}
export const useVariantsStore = defineStore('admin-variants', {
state: () => ({
variantsByPage: variantSeed as VariantMap
}),
getters: {
listByPage: (state) => (pageId: string) => {
const list = state.variantsByPage[pageId] ?? []
return [...list].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
},
countByPage: (state) => (pageId: string) => {
return state.variantsByPage[pageId]?.length ?? 0
},
totalLeadsByPage: (state) => (pageId: string) => {
return (state.variantsByPage[pageId] ?? []).reduce((sum, item) => sum + item.leadCount, 0)
}
},
actions: {
createVariant(payload: CreateVariantInput) {
const normalizedName = payload.name.trim()
const pageId = payload.pageId.trim()
const description = payload.description.trim()
if (pageId.length === 0 || normalizedName.length === 0) {
return
}
const pageVariants = this.variantsByPage[pageId] ?? []
const newVariant: PageVariant = {
id: `v-${Date.now().toString(36)}`,
pageId,
name: normalizedName,
description,
trafficWeight: clampTrafficWeight(payload.trafficWeight),
isActive: true,
leadCount: 0,
updatedAt: new Date().toISOString()
}
this.variantsByPage[pageId] = [newVariant, ...pageVariants]
},
updateVariant(payload: UpdateVariantInput) {
const pageId = payload.pageId.trim()
const variants = this.variantsByPage[pageId]
if (!variants) {
return
}
const idx = variants.findIndex((item) => item.id === payload.id)
if (idx === -1) {
return
}
const target = variants[idx]
variants[idx] = {
...target,
...payload,
updatedAt: new Date().toISOString(),
pageId
}
},
toggleActive(pageId: string, variantId: string) {
const variants = this.variantsByPage[pageId]
if (!variants) {
return
}
const idx = variants.findIndex((item) => item.id === variantId)
if (idx === -1) {
return
}
variants[idx] = {
...variants[idx],
isActive: !variants[idx].isActive,
updatedAt: new Date().toISOString()
}
},
removeVariant(pageId: string, variantId: string) {
const variants = this.variantsByPage[pageId]
if (!variants) {
return
}
this.variantsByPage[pageId] = variants.filter((variant) => variant.id !== variantId)
}
}
})