admin felület különválasztva
This commit is contained in:
259
frontend_app/src/stores/adminPackages.ts
Normal file
259
frontend_app/src/stores/adminPackages.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* 📦 Admin Packages Store
|
||||
*
|
||||
* Pinia store for managing SubscriptionTier packages.
|
||||
* Mirrors the backend admin_packages.py API endpoints.
|
||||
*
|
||||
* Endpoints:
|
||||
* GET /admin/packages/ — List all packages
|
||||
* POST /admin/packages/ — Create a new package
|
||||
* PATCH /admin/packages/{id} — Update a package
|
||||
* DELETE /admin/packages/{id} — Soft-delete (deactivate) a package
|
||||
*/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import api from '../api/axios'
|
||||
|
||||
// ── TypeScript Interfaces (mirroring backend Pydantic schemas) ───────────────
|
||||
|
||||
export interface PricingModel {
|
||||
monthly_price: number
|
||||
yearly_price: number
|
||||
currency: string
|
||||
credit_price?: number | null
|
||||
}
|
||||
|
||||
export interface PricingZoneModel {
|
||||
monthly_price: number
|
||||
yearly_price: number
|
||||
currency: string
|
||||
credit_price?: number | null
|
||||
}
|
||||
|
||||
export interface MarketingModel {
|
||||
subtitle?: string | null
|
||||
badge?: string | null
|
||||
highlight_color?: string | null
|
||||
}
|
||||
|
||||
export interface AllowancesModel {
|
||||
max_vehicles: number
|
||||
max_garages: number
|
||||
monthly_free_credits: number
|
||||
}
|
||||
|
||||
export interface LifecycleModel {
|
||||
is_public: boolean
|
||||
available_until?: string | null
|
||||
}
|
||||
|
||||
export interface AffiliateModel {
|
||||
commission_rate_percent: number
|
||||
referral_bonus_credits: number
|
||||
}
|
||||
|
||||
export interface SubscriptionRulesModel {
|
||||
type: 'private' | 'corporate'
|
||||
display_name?: string | null
|
||||
pricing: PricingModel
|
||||
pricing_zones?: Record<string, PricingZoneModel> | null
|
||||
allowances: AllowancesModel
|
||||
entitlements: string[]
|
||||
affiliate: AffiliateModel
|
||||
lifecycle?: LifecycleModel | null
|
||||
marketing?: MarketingModel | null
|
||||
}
|
||||
|
||||
export interface SubscriptionTierItem {
|
||||
id: number
|
||||
name: string
|
||||
rules: SubscriptionRulesModel
|
||||
is_custom: boolean
|
||||
}
|
||||
|
||||
export interface SubscriptionTierListResponse {
|
||||
total: number
|
||||
tiers: SubscriptionTierItem[]
|
||||
}
|
||||
|
||||
export interface SubscriptionTierCreatePayload {
|
||||
name: string
|
||||
rules: SubscriptionRulesModel
|
||||
is_custom?: boolean
|
||||
}
|
||||
|
||||
export interface SubscriptionTierUpdatePayload {
|
||||
name?: string
|
||||
rules?: SubscriptionRulesModel
|
||||
is_custom?: boolean
|
||||
}
|
||||
|
||||
export interface DeletePackageResponse {
|
||||
status: string
|
||||
message: string
|
||||
tier_id: number
|
||||
is_public: boolean
|
||||
}
|
||||
|
||||
// ── Available Entitlements (service codes) ───────────────────────────────────
|
||||
|
||||
export const AVAILABLE_ENTITLEMENTS: string[] = [
|
||||
'SRV_DATA_EXPORT',
|
||||
'SRV_AI_UPLOAD',
|
||||
'SRV_ACCOUNTING_SYNC',
|
||||
'SRV_API_ACCESS',
|
||||
'SRV_MULTI_USER',
|
||||
'SRV_PRIORITY_SUPPORT',
|
||||
'SRV_ADVANCED_ANALYTICS',
|
||||
'SRV_CUSTOM_REPORTS',
|
||||
'SRV_GARAGE_SHARING',
|
||||
'SRV_SERVICE_HISTORY',
|
||||
'SRV_FUEL_MANAGEMENT',
|
||||
'SRV_MAINTENANCE_ALERTS',
|
||||
'SRV_ODOMETER_TRACKING',
|
||||
'SRV_DOCUMENT_STORAGE',
|
||||
'SRV_INSURANCE_INTEGRATION',
|
||||
]
|
||||
|
||||
// ── Store ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const useAdminPackagesStore = defineStore('adminPackages', () => {
|
||||
// ── State ────────────────────────────────────────────────────────────────
|
||||
const tiers = ref<SubscriptionTierItem[]>([])
|
||||
const total = ref(0)
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// ── Actions ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fetch all subscription packages from GET /admin/packages/.
|
||||
*/
|
||||
async function fetchPackages(params: {
|
||||
skip?: number
|
||||
limit?: number
|
||||
include_hidden?: boolean
|
||||
type_filter?: string
|
||||
date_from?: string
|
||||
date_until?: string
|
||||
} = {}) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const queryParams: Record<string, any> = {
|
||||
skip: params.skip ?? 0,
|
||||
limit: params.limit ?? 100,
|
||||
}
|
||||
if (params.include_hidden) {
|
||||
queryParams.include_hidden = true
|
||||
}
|
||||
if (params.type_filter) {
|
||||
queryParams.type_filter = params.type_filter
|
||||
}
|
||||
if (params.date_from) {
|
||||
queryParams.date_from = params.date_from
|
||||
}
|
||||
if (params.date_until) {
|
||||
queryParams.date_until = params.date_until
|
||||
}
|
||||
|
||||
const res = await api.get('/admin/packages', { params: queryParams })
|
||||
const data = res.data as SubscriptionTierListResponse
|
||||
console.log('[AdminPackages] API response:', data)
|
||||
console.log('[AdminPackages] tiers array:', data.tiers)
|
||||
console.log('[AdminPackages] tiers length:', data.tiers?.length)
|
||||
tiers.value = data.tiers
|
||||
total.value = data.total
|
||||
return data
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || 'Failed to fetch packages'
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new subscription package via POST /admin/packages/.
|
||||
* NOTE: Do NOT send the `id` field — PostgreSQL auto-increments it.
|
||||
*/
|
||||
async function createPackage(payload: SubscriptionTierCreatePayload): Promise<SubscriptionTierItem> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.post('/admin/packages', payload)
|
||||
const created = res.data as SubscriptionTierItem
|
||||
// Refresh the list
|
||||
await fetchPackages({ include_hidden: true })
|
||||
return created
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || 'Failed to create package'
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing package via PATCH /admin/packages/{id}.
|
||||
*/
|
||||
async function updatePackage(
|
||||
tierId: number,
|
||||
payload: SubscriptionTierUpdatePayload,
|
||||
): Promise<SubscriptionTierItem> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.patch(`/admin/packages/${tierId}`, payload)
|
||||
const updated = res.data as SubscriptionTierItem
|
||||
// Refresh the list
|
||||
await fetchPackages({ include_hidden: true })
|
||||
return updated
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || 'Failed to update package'
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft-delete (deactivate) a package via DELETE /admin/packages/{id}.
|
||||
* Sets lifecycle.is_public = false.
|
||||
*/
|
||||
async function deletePackage(tierId: number): Promise<DeletePackageResponse> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.delete(`/admin/packages/${tierId}`)
|
||||
const result = res.data as DeletePackageResponse
|
||||
// Refresh the list
|
||||
await fetchPackages({ include_hidden: true })
|
||||
return result
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || 'Failed to delete package'
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
tiers,
|
||||
total,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Actions
|
||||
fetchPackages,
|
||||
createPackage,
|
||||
updatePackage,
|
||||
deletePackage,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user