feat:구독관리 탭 실제 구독자 데이터 표시 (sales_contract_products 기반)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -269,7 +269,40 @@ public function customerTab(Request $request): View
|
||||
*/
|
||||
public function subscriptionTab(Request $request): View
|
||||
{
|
||||
return view('finance.settlement.partials.subscription-tab');
|
||||
// subscription_fee > 0인 계약상품이 있는 관리건 조회
|
||||
$query = SalesTenantManagement::with([
|
||||
'tenant', 'tenantProspect', 'salesPartner.user',
|
||||
'manager', 'contractProducts.product', 'contractProducts.category',
|
||||
])
|
||||
->contracted()
|
||||
->whereHas('contractProducts', fn($q) => $q->where('subscription_fee', '>', 0));
|
||||
|
||||
// 검색 필터
|
||||
if ($search = $request->input('search')) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->whereHas('tenant', fn($t) => $t->where('company_name', 'like', "%{$search}%"))
|
||||
->orWhereHas('tenantProspect', fn($t) => $t->where('company_name', 'like', "%{$search}%"));
|
||||
});
|
||||
}
|
||||
|
||||
// 상태 필터
|
||||
if ($status = $request->input('status')) {
|
||||
if ($status !== 'all') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
}
|
||||
|
||||
$managements = $query->orderBy('contracted_at', 'desc')->get();
|
||||
|
||||
// 통계 계산
|
||||
$stats = [
|
||||
'activeCount' => $managements->count(),
|
||||
'monthlyRecurring' => $managements->sum(fn($m) => $m->contractProducts->sum('subscription_fee')),
|
||||
'totalProducts' => $managements->sum(fn($m) => $m->contractProducts->where('subscription_fee', '>', 0)->count()),
|
||||
];
|
||||
$stats['yearlyRecurring'] = $stats['monthlyRecurring'] * 12;
|
||||
|
||||
return view('finance.settlement.partials.subscription-tab', compact('managements', 'stats'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -369,55 +369,6 @@ function onTenantSelect(managementId) {
|
||||
});
|
||||
}
|
||||
|
||||
// 구독관리 탭 Alpine 컴포넌트
|
||||
function subscriptionManager() {
|
||||
return {
|
||||
items: [], stats: { activeCount: 0, monthlyRecurring: 0, yearlyRecurring: 0, totalUsers: 0 },
|
||||
loading: false, saving: false, showModal: false, modalMode: 'add', editingId: null,
|
||||
searchTerm: '', filterStatus: 'all', filterPlan: 'all',
|
||||
form: { customer: '', plan: 'Starter', monthlyFee: 0, billingCycle: 'monthly', startDate: '', nextBilling: '', status: 'active', users: 0, memo: '' },
|
||||
formatCurrency(val) { return Number(val || 0).toLocaleString(); },
|
||||
filteredItems() {
|
||||
return this.items.filter(item => {
|
||||
const matchSearch = !this.searchTerm || (item.customer || '').toLowerCase().includes(this.searchTerm.toLowerCase());
|
||||
const matchStatus = this.filterStatus === 'all' || item.status === this.filterStatus;
|
||||
const matchPlan = this.filterPlan === 'all' || item.plan === this.filterPlan;
|
||||
return matchSearch && matchStatus && matchPlan;
|
||||
});
|
||||
},
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await fetch('/finance/subscriptions/list');
|
||||
const data = await res.json();
|
||||
if (data.success) { this.items = data.data; this.stats = data.stats; }
|
||||
} finally { this.loading = false; }
|
||||
},
|
||||
openModal(mode, item = null) {
|
||||
this.modalMode = mode; this.editingId = item?.id || null;
|
||||
this.form = item ? { ...item } : { customer: '', plan: 'Starter', monthlyFee: 0, billingCycle: 'monthly', startDate: new Date().toISOString().split('T')[0], nextBilling: '', status: 'active', users: 0, memo: '' };
|
||||
this.showModal = true;
|
||||
},
|
||||
async saveItem() {
|
||||
if (!this.form.customer || !this.form.monthlyFee) { alert('필수 항목을 입력해주세요.'); return; }
|
||||
this.saving = true;
|
||||
try {
|
||||
const url = this.modalMode === 'add' ? '/finance/subscriptions/store' : '/finance/subscriptions/' + this.editingId;
|
||||
const body = { ...this.form, monthlyFee: parseInt(this.form.monthlyFee) || 0, users: parseInt(this.form.users) || 0 };
|
||||
const res = await fetch(url, { method: this.modalMode === 'add' ? 'POST' : 'PUT', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify(body) });
|
||||
const data = await res.json();
|
||||
if (!res.ok) { alert(data.errors ? Object.values(data.errors).flat().join('\n') : data.message || '저장 실패'); return; }
|
||||
this.showModal = false; this.fetchData();
|
||||
} finally { this.saving = false; }
|
||||
},
|
||||
async deleteItem(id) {
|
||||
if (!confirm('정말 삭제하시겠습니까?')) return;
|
||||
await fetch('/finance/subscriptions/' + id, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content } });
|
||||
this.fetchData();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 초기 탭이 기본(commission)이 아닌 경우 HTMX 콘텐츠 자동 로드
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const initialTab = '{{ $initialTab }}';
|
||||
|
||||
@@ -1,54 +1,52 @@
|
||||
{{-- 구독관리 탭 (Blade + Alpine.js) --}}
|
||||
<div x-data="subscriptionManager()" x-init="fetchData()">
|
||||
{{-- 구독관리 탭 (서버 렌더링 Blade) --}}
|
||||
<div class="space-y-6">
|
||||
{{-- 통계 카드 --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-gray-400">
|
||||
<p class="text-sm text-gray-500">활성 구독</p>
|
||||
<p class="text-xl font-bold text-gray-700" x-text="stats.activeCount + '개'">0개</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-teal-500">
|
||||
<p class="text-sm text-gray-500">활성 구독</p>
|
||||
<p class="text-xl font-bold text-teal-600">{{ $stats['activeCount'] }}건</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-indigo-500">
|
||||
<p class="text-sm text-gray-500">월 반복 수익(MRR)</p>
|
||||
<p class="text-xl font-bold text-teal-600" x-text="formatCurrency(stats.monthlyRecurring) + '원'">0원</p>
|
||||
<p class="text-xl font-bold text-indigo-600">{{ number_format($stats['monthlyRecurring']) }}원</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-emerald-500">
|
||||
<p class="text-sm text-gray-500">연 반복 수익(ARR)</p>
|
||||
<p class="text-xl font-bold text-emerald-600" x-text="formatCurrency(stats.yearlyRecurring) + '원'">0원</p>
|
||||
<p class="text-xl font-bold text-emerald-600">{{ number_format($stats['yearlyRecurring']) }}원</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-gray-400">
|
||||
<p class="text-sm text-gray-500">총 사용자</p>
|
||||
<p class="text-xl font-bold text-gray-700" x-text="stats.totalUsers + '명'">0명</p>
|
||||
<p class="text-sm text-gray-500">구독 상품</p>
|
||||
<p class="text-xl font-bold text-gray-700">{{ $stats['totalProducts'] }}개</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 필터 + 등록 버튼 --}}
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
||||
<div class="flex flex-wrap items-end gap-4">
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">검색</label>
|
||||
<input type="text" x-model="searchTerm" placeholder="고객사 검색" class="w-full rounded-lg border-gray-300 focus:border-teal-500 focus:ring-teal-500">
|
||||
</div>
|
||||
{{-- 검색/필터 --}}
|
||||
<div class="bg-white rounded-lg shadow-sm p-4">
|
||||
<form hx-get="{{ route('finance.settlement.subscription') }}"
|
||||
hx-target="#subscription-content"
|
||||
hx-trigger="submit"
|
||||
class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">플랜</label>
|
||||
<select x-model="filterPlan" class="rounded-lg border-gray-300 focus:border-teal-500 focus:ring-teal-500">
|
||||
<option value="all">전체</option>
|
||||
<option value="Starter">Starter</option>
|
||||
<option value="Business">Business</option>
|
||||
<option value="Enterprise">Enterprise</option>
|
||||
</select>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">검색</label>
|
||||
<input type="text" name="search" placeholder="고객사명"
|
||||
value="{{ request('search') }}"
|
||||
class="w-full rounded-lg border-gray-300 focus:border-teal-500 focus:ring-teal-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">상태</label>
|
||||
<select x-model="filterStatus" class="rounded-lg border-gray-300 focus:border-teal-500 focus:ring-teal-500">
|
||||
<select name="status" class="w-full rounded-lg border-gray-300 focus:border-teal-500 focus:ring-teal-500">
|
||||
<option value="all">전체</option>
|
||||
<option value="active">활성</option>
|
||||
<option value="trial">체험</option>
|
||||
<option value="cancelled">해지</option>
|
||||
<option value="contracted" {{ request('status') == 'contracted' ? 'selected' : '' }}>계약완료</option>
|
||||
<option value="onboarding" {{ request('status') == 'onboarding' ? 'selected' : '' }}>온보딩</option>
|
||||
<option value="active" {{ request('status') == 'active' ? 'selected' : '' }}>활성</option>
|
||||
</select>
|
||||
</div>
|
||||
<button @click="openModal('add')" class="px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white rounded-lg transition-colors">
|
||||
+ 등록
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="w-full px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white rounded-lg transition-colors">
|
||||
조회
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- 테이블 --}}
|
||||
@@ -57,97 +55,102 @@
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">고객사</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">플랜</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">월 요금</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">결제주기</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">다음 결제</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">사용자</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">상태</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">관리</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">고객사</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">담당 파트너</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">구독 상품</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">월 구독료</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">계약일</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">개발상태</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">입금상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<template x-if="loading">
|
||||
<tr><td colspan="8" class="px-4 py-8 text-center text-gray-400">로딩 중...</td></tr>
|
||||
</template>
|
||||
<template x-if="!loading && filteredItems().length === 0">
|
||||
<tr><td colspan="8" class="px-4 py-8 text-center text-gray-400">데이터가 없습니다.</td></tr>
|
||||
</template>
|
||||
<template x-for="item in filteredItems()" :key="item.id">
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse ($managements as $mgmt)
|
||||
@php
|
||||
$companyName = $mgmt->tenant?->company_name ?? $mgmt->tenantProspect?->company_name ?? '-';
|
||||
$partnerName = $mgmt->salesPartner?->user?->name ?? '-';
|
||||
$subscriptionProducts = $mgmt->contractProducts->where('subscription_fee', '>', 0);
|
||||
$monthlyTotal = $subscriptionProducts->sum('subscription_fee');
|
||||
|
||||
$statusColor = match ($mgmt->hq_status) {
|
||||
'review' => 'bg-purple-100 text-purple-700',
|
||||
'planning' => 'bg-blue-100 text-blue-700',
|
||||
'coding' => 'bg-indigo-100 text-indigo-700',
|
||||
'dev_test' => 'bg-cyan-100 text-cyan-700',
|
||||
'dev_done' => 'bg-teal-100 text-teal-700',
|
||||
'int_test' => 'bg-amber-100 text-amber-700',
|
||||
'handover' => 'bg-green-100 text-green-700',
|
||||
default => 'bg-gray-100 text-gray-700',
|
||||
};
|
||||
|
||||
$contractStatusColor = match ($mgmt->status) {
|
||||
'contracted' => 'bg-blue-100 text-blue-700',
|
||||
'onboarding' => 'bg-amber-100 text-amber-700',
|
||||
'active' => 'bg-green-100 text-green-700',
|
||||
default => 'bg-gray-100 text-gray-700',
|
||||
};
|
||||
$contractStatusLabel = \App\Models\Sales\SalesTenantManagement::$statusLabels[$mgmt->status] ?? $mgmt->status;
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50">
|
||||
{{-- 고객사 --}}
|
||||
<td class="px-4 py-3">
|
||||
<div class="text-sm font-medium text-gray-900" x-text="item.customer"></div>
|
||||
<div x-show="item.memo" class="text-xs text-gray-400" x-text="item.memo"></div>
|
||||
<div class="text-sm font-medium text-gray-900">{{ $companyName }}</div>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium {{ $contractStatusColor }}">{{ $contractStatusLabel }}</span>
|
||||
</td>
|
||||
|
||||
{{-- 담당 파트너 --}}
|
||||
<td class="px-4 py-3 text-sm text-gray-900">{{ $partnerName }}</td>
|
||||
|
||||
{{-- 구독 상품 --}}
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium"
|
||||
:class="{'bg-gray-100 text-gray-700': item.plan === 'Starter', 'bg-blue-100 text-blue-700': item.plan === 'Business', 'bg-purple-100 text-purple-700': item.plan === 'Enterprise'}"
|
||||
x-text="item.plan"></span>
|
||||
<div class="space-y-1">
|
||||
@foreach ($subscriptionProducts as $cp)
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-900">{{ $cp->product?->name ?? $cp->category?->name ?? '상품' }}</span>
|
||||
<span class="text-xs text-gray-500">{{ number_format($cp->subscription_fee) }}원/월</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-right font-medium text-teal-600" x-text="formatCurrency(item.monthlyFee) + '원'"></td>
|
||||
<td class="px-4 py-3 text-sm text-center text-gray-600" x-text="item.billingCycle === 'monthly' ? '월간' : '연간'"></td>
|
||||
<td class="px-4 py-3 text-sm text-center text-gray-600" x-text="item.nextBilling || '-'"></td>
|
||||
<td class="px-4 py-3 text-sm text-center text-gray-600" x-text="item.users + '명'"></td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<span class="px-2 py-0.5 rounded-full text-xs font-medium"
|
||||
:class="{'bg-emerald-100 text-emerald-700': item.status === 'active', 'bg-blue-100 text-blue-700': item.status === 'trial', 'bg-rose-100 text-rose-700': item.status === 'cancelled', 'bg-amber-100 text-amber-700': item.status === 'paused'}"
|
||||
x-text="{'active':'활성','trial':'체험','cancelled':'해지','paused':'일시정지'}[item.status] || item.status"></span>
|
||||
|
||||
{{-- 월 구독료 합계 --}}
|
||||
<td class="px-4 py-3 text-sm text-right font-medium text-teal-600">
|
||||
{{ number_format($monthlyTotal) }}원
|
||||
</td>
|
||||
|
||||
{{-- 계약일 --}}
|
||||
<td class="px-4 py-3 text-sm text-center text-gray-600">
|
||||
{{ $mgmt->contracted_at ? \Carbon\Carbon::parse($mgmt->contracted_at)->format('Y-m-d') : '-' }}
|
||||
</td>
|
||||
|
||||
{{-- 개발상태 --}}
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button @click="openModal('edit', item)" class="p-1 text-gray-400 hover:text-blue-500" title="수정">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||
</button>
|
||||
<button @click="deleteItem(item.id)" class="p-1 text-gray-400 hover:text-red-500" title="삭제">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
</button>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium {{ $statusColor }}">
|
||||
{{ $mgmt->hq_status_label }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{{-- 입금상태 --}}
|
||||
<td class="px-4 py-3 text-center">
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium {{ $mgmt->deposit_status === 'paid' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700' }}">
|
||||
계약금 {{ $mgmt->deposit_status === 'paid' ? '완료' : '대기' }}
|
||||
</span>
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium {{ $mgmt->balance_status === 'paid' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700' }}">
|
||||
잔금 {{ $mgmt->balance_status === 'paid' ? '완료' : '대기' }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-4 py-8 text-center text-gray-500">
|
||||
구독 상품이 있는 고객사가 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 모달 --}}
|
||||
<div x-show="showModal" x-cloak class="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div class="bg-white rounded-xl p-6 w-full max-w-lg mx-4 max-h-[90vh] overflow-y-auto" @click.outside="showModal = false">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="text-lg font-bold text-gray-900" x-text="modalMode === 'add' ? '구독 등록' : '구독 수정'"></h3>
|
||||
<button @click="showModal = false" class="p-1 hover:bg-gray-100 rounded-lg">
|
||||
<svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">고객사 *</label><input type="text" x-model="form.customer" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">플랜</label>
|
||||
<select x-model="form.plan" class="w-full px-3 py-2 border rounded-lg"><option value="Starter">Starter</option><option value="Business">Business</option><option value="Enterprise">Enterprise</option></select>
|
||||
</div>
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">월 요금 *</label><input type="number" x-model="form.monthlyFee" class="w-full px-3 py-2 border rounded-lg text-right"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">결제주기</label>
|
||||
<select x-model="form.billingCycle" class="w-full px-3 py-2 border rounded-lg"><option value="monthly">월간</option><option value="yearly">연간</option></select>
|
||||
</div>
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">사용자 수</label><input type="number" x-model="form.users" class="w-full px-3 py-2 border rounded-lg text-right"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">시작일</label><input type="date" x-model="form.startDate" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">다음 결제일</label><input type="date" x-model="form.nextBilling" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">상태</label>
|
||||
<select x-model="form.status" class="w-full px-3 py-2 border rounded-lg"><option value="active">활성</option><option value="trial">체험</option><option value="paused">일시정지</option><option value="cancelled">해지</option></select>
|
||||
</div>
|
||||
<div><label class="block text-sm font-medium text-gray-700 mb-1">메모</label><input type="text" x-model="form.memo" class="w-full px-3 py-2 border rounded-lg"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3 mt-6">
|
||||
<button @click="showModal = false" class="flex-1 px-4 py-2 border text-gray-700 rounded-lg hover:bg-gray-50">취소</button>
|
||||
<button @click="saveItem()" :disabled="saving" class="flex-1 px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white rounded-lg disabled:opacity-50" x-text="saving ? '저장 중...' : '저장'">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user