ui(step-3): apply reference layout to 14 more management pages
- pricing, credit, coupon, discount, tax, order, invoice: white header, data-table/table-card, navy buttons, inline styles removed - review, leads, jobs, notifications, support, report, ledger: same pattern + orange tab underlines where applicable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0bb59b99ef
commit
5cfa4b89be
14 changed files with 1906 additions and 1859 deletions
|
|
@ -142,35 +142,35 @@ export default function CouponPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Coupon Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Reusable coupon codes for package checkout</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Coupon Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Reusable coupon codes for package checkout</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style="display:flex;border-bottom:2px solid #e2e8f0;margin-bottom:24px;gap:0;overflow-x:auto;">
|
||||
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'list' ? ' active' : ''}`}
|
||||
class={activeTab() === 'list' ? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium' : 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab('list')}
|
||||
>
|
||||
Coupons
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'create' ? ' active' : ''}`}
|
||||
class={activeTab() === 'create' ? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium' : 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => { resetForm(); setActiveTab('create'); }}
|
||||
>
|
||||
{form().id ? 'Edit Coupon' : 'Create Coupon'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<Show when={activeTab() === 'list'}>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
|
|
@ -195,12 +195,12 @@ export default function CouponPage() {
|
|||
<Show when={!coupons.loading && !coupons.error && (coupons()?.length ?? 0) > 0}>
|
||||
<For each={coupons()}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a;font-family:monospace">{item.code}</td>
|
||||
<td style="color:#475569">{item.title || '—'}</td>
|
||||
<td style="color:#475569">{item.type}</td>
|
||||
<td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `₹${item.value}`}</td>
|
||||
<td style="color:#475569">{item.usage_limit != null ? item.usage_limit : '—'}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900" style="font-family:monospace">{item.code}</td>
|
||||
<td class="text-slate-500">{item.title || '—'}</td>
|
||||
<td class="text-slate-500">{item.type}</td>
|
||||
<td class="text-slate-500">{item.type === 'PERCENT' ? `${item.value}%` : `₹${item.value}`}</td>
|
||||
<td class="text-slate-500">{item.usage_limit != null ? item.usage_limit : '—'}</td>
|
||||
<td>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active ? 'active' : ''}`}>
|
||||
{item.is_active ? 'Active' : 'Inactive'}
|
||||
|
|
@ -225,7 +225,7 @@ export default function CouponPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'create'}>
|
||||
|
|
@ -320,7 +320,7 @@ export default function CouponPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : (form().id ? 'Update Coupon' : 'Save Coupon')}
|
||||
</button>
|
||||
<Show when={form().id}>
|
||||
|
|
@ -330,6 +330,8 @@ export default function CouponPage() {
|
|||
</form>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,20 +136,19 @@ export default function CreditPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Credit Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Audit TraceCoin balances and adjust credits</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Credit Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Audit TraceCoin balances and adjust credits</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style="display:flex;border-bottom:2px solid #e2e8f0;margin-bottom:24px;gap:0;overflow-x:auto;">
|
||||
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
|
||||
<For each={tabs}>
|
||||
{(tab) => (
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === tab.key ? ' active' : ''}`}
|
||||
class={activeTab() === tab.key ? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium' : 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
|
|
@ -158,6 +157,7 @@ export default function CreditPage() {
|
|||
</For>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
{/* Balance & Ledger Tab */}
|
||||
<Show when={activeTab() === 'ledger'}>
|
||||
<div style="display:flex;flex-direction:column;gap:24px">
|
||||
|
|
@ -170,10 +170,11 @@ export default function CreditPage() {
|
|||
value={userId()}
|
||||
onInput={(e) => setUserId(e.currentTarget.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
style="flex:1;padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
|
||||
class="rounded-lg border border-gray-200 px-3 py-2 text-sm"
|
||||
style="flex:1"
|
||||
/>
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors"
|
||||
onClick={handleSearch}
|
||||
disabled={searchLoading()}
|
||||
>
|
||||
|
|
@ -193,14 +194,14 @@ export default function CreditPage() {
|
|||
<p style="font-size:12px;color:#bfdbfe;margin:8px 0 0">User: {searchedUserId()}</p>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:20px;overflow:hidden">
|
||||
<div class="table-card" style="overflow:hidden">
|
||||
<h3 style="margin:0 0 16px;font-size:15px;font-weight:700;color:#0f172a">TraceCoin Ledger</h3>
|
||||
<Show when={ledger().length === 0}>
|
||||
<p style="text-align:center;padding:32px;color:#94a3b8;font-style:italic">No transactions found for this account.</p>
|
||||
</Show>
|
||||
<Show when={ledger().length > 0}>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
|
|
@ -213,7 +214,7 @@ export default function CreditPage() {
|
|||
<tbody>
|
||||
<For each={ledger()}>
|
||||
{(entry) => (
|
||||
<tr>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td>
|
||||
<span
|
||||
style={`display:inline-block;padding:2px 8px;border-radius:999px;font-size:10px;font-weight:700;text-transform:uppercase;background:${entry.transactionType === 'ADD' ? '#dcfce7' : '#fee2e2'};color:${entry.transactionType === 'ADD' ? '#15803d' : '#b91c1c'}`}
|
||||
|
|
@ -221,16 +222,16 @@ export default function CreditPage() {
|
|||
{entry.transactionType}
|
||||
</span>
|
||||
</td>
|
||||
<td style={`font-weight:700;${entry.transactionType === 'ADD' ? 'color:#16a34a' : 'color:#dc2626'}`}>
|
||||
<td class={entry.transactionType === 'ADD' ? 'font-semibold text-slate-900' : 'font-semibold text-slate-900'} style={entry.transactionType === 'ADD' ? 'color:#16a34a' : 'color:#dc2626'}>
|
||||
{entry.transactionType === 'ADD' ? '+' : '-'}{entry.amount}
|
||||
</td>
|
||||
<td style="font-size:12px;color:#64748b;font-family:monospace">
|
||||
<td class="text-slate-500" style="font-size:12px;font-family:monospace">
|
||||
{entry.referenceId ? entry.referenceId.substring(0, 18) : '—'}
|
||||
</td>
|
||||
<td style="font-size:12px;color:#94a3b8">
|
||||
<td class="text-slate-500" style="font-size:12px">
|
||||
{entry.expiresAt ? new Date(entry.expiresAt).toLocaleDateString() : '—'}
|
||||
</td>
|
||||
<td style="font-size:12px;color:#94a3b8">
|
||||
<td class="text-slate-500" style="font-size:12px">
|
||||
{entry.createdAt ? new Date(entry.createdAt).toLocaleString() : '—'}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -240,7 +241,7 @@ export default function CreditPage() {
|
|||
</table>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
@ -316,7 +317,7 @@ export default function CreditPage() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={adjLoading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={adjLoading()}>
|
||||
{adjLoading() ? 'Adjusting...' : 'Apply Adjustment'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -356,7 +357,7 @@ export default function CreditPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={reconLoading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={reconLoading()}>
|
||||
{reconLoading() ? 'Running...' : 'Run Reconciliation'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -368,9 +369,9 @@ export default function CreditPage() {
|
|||
<p style="color:#16a34a;font-weight:600;font-size:14px">No discrepancies found.</p>
|
||||
</Show>
|
||||
<Show when={(reconResults()?.length ?? 0) > 0}>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
|
|
@ -382,10 +383,10 @@ export default function CreditPage() {
|
|||
<tbody>
|
||||
<For each={reconResults()!}>
|
||||
{(row) => (
|
||||
<tr>
|
||||
<td style="font-family:monospace;font-size:13px">{row.userId}</td>
|
||||
<td>{row.expectedBalance}</td>
|
||||
<td>{row.actualBalance}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900" style="font-family:monospace;font-size:13px">{row.userId}</td>
|
||||
<td class="text-slate-500">{row.expectedBalance}</td>
|
||||
<td class="text-slate-500">{row.actualBalance}</td>
|
||||
<td style={row.discrepancy !== 0 ? 'color:#dc2626;font-weight:700' : 'color:#16a34a'}>
|
||||
{row.discrepancy}
|
||||
</td>
|
||||
|
|
@ -395,11 +396,13 @@ export default function CreditPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,35 +146,35 @@ export default function DiscountPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Discount Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Automatic discounts applied before coupons</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Discount Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Automatic discounts applied before coupons</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style="display:flex;border-bottom:2px solid #e2e8f0;margin-bottom:24px;gap:0;overflow-x:auto;">
|
||||
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'list' ? ' active' : ''}`}
|
||||
class={activeTab() === 'list' ? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium' : 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab('list')}
|
||||
>
|
||||
Discounts
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'create' ? ' active' : ''}`}
|
||||
class={activeTab() === 'create' ? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium' : 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => { resetForm(); setActiveTab('create'); }}
|
||||
>
|
||||
{form().id ? 'Edit Discount' : 'Create Discount'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<Show when={activeTab() === 'list'}>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
|
|
@ -199,12 +199,12 @@ export default function DiscountPage() {
|
|||
<Show when={!discounts.loading && !discounts.error && (discounts()?.length ?? 0) > 0}>
|
||||
<For each={discounts()}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{item.title || '—'}</td>
|
||||
<td style="color:#475569">{item.scope}</td>
|
||||
<td style="color:#475569">{getTarget(item)}</td>
|
||||
<td style="color:#475569">{item.type}</td>
|
||||
<td style="color:#475569">{item.type === 'PERCENT' ? `${item.value}%` : `₹${item.value}`}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900">{item.title || '—'}</td>
|
||||
<td class="text-slate-500">{item.scope}</td>
|
||||
<td class="text-slate-500">{getTarget(item)}</td>
|
||||
<td class="text-slate-500">{item.type}</td>
|
||||
<td class="text-slate-500">{item.type === 'PERCENT' ? `${item.value}%` : `₹${item.value}`}</td>
|
||||
<td>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active ? 'active' : ''}`}>
|
||||
{item.is_active ? 'Active' : 'Inactive'}
|
||||
|
|
@ -228,7 +228,7 @@ export default function DiscountPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'create'}>
|
||||
|
|
@ -324,7 +324,7 @@ export default function DiscountPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : (form().id ? 'Update Discount' : 'Save Discount')}
|
||||
</button>
|
||||
<Show when={form().id}>
|
||||
|
|
@ -334,6 +334,8 @@ export default function DiscountPage() {
|
|||
</form>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,26 +32,27 @@ export default function InvoicePage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Invoice Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View and download all platform invoices.</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Invoice Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">View and download all platform invoices.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<div style="margin-bottom:16px">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search invoices by number, user, package, or status..."
|
||||
value={search()}
|
||||
onInput={(e) => setSearch(e.currentTarget.value)}
|
||||
style="padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;min-width:320px"
|
||||
class="rounded-lg border border-gray-200 px-3 py-2 text-sm"
|
||||
style="min-width:320px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice #</th>
|
||||
|
|
@ -76,18 +77,18 @@ export default function InvoicePage() {
|
|||
</Show>
|
||||
<Show when={!invoices.loading && !invoices.error && filteredInvoices().length > 0}>
|
||||
{filteredInvoices().map((item) => (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a;font-family:monospace">{item.invoice_number || item.id}</td>
|
||||
<td style="color:#475569">{item.user_id || '—'}</td>
|
||||
<td style="color:#475569">{item.package_name || '—'}</td>
|
||||
<td style="color:#475569">₹{item.total != null ? (item.total / 100).toFixed(2) : '—'}</td>
|
||||
<td style="color:#475569">₹{item.tax != null ? (item.tax / 100).toFixed(2) : '—'}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900" style="font-family:monospace">{item.invoice_number || item.id}</td>
|
||||
<td class="text-slate-500">{item.user_id || '—'}</td>
|
||||
<td class="text-slate-500">{item.package_name || '—'}</td>
|
||||
<td class="text-slate-500">₹{item.total != null ? (item.total / 100).toFixed(2) : '—'}</td>
|
||||
<td class="text-slate-500">₹{item.tax != null ? (item.tax / 100).toFixed(2) : '—'}</td>
|
||||
<td>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.status === 'PAID' || item.status === 'ISSUED' ? 'active' : ''}`}>
|
||||
{item.status || '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td style="color:#475569">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||||
<td class="text-slate-500">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||||
<td>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<Show when={item.download_url || item.pdf_url}>
|
||||
|
|
@ -112,7 +113,9 @@ export default function InvoicePage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,13 +63,13 @@ export default function JobsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Jobs Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review live company job postings</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Jobs Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Review live company job postings</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<div style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -87,9 +87,9 @@ export default function JobsPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
|
|
@ -114,25 +114,25 @@ export default function JobsPage() {
|
|||
<Show when={!jobs.loading && !jobs.error && filtered().length > 0}>
|
||||
<For each={filtered()}>
|
||||
{(job) => (
|
||||
<tr>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td>
|
||||
<div style="font-weight:600;color:#0f172a">{job.title || '—'}</div>
|
||||
<div class="font-semibold text-slate-900">{job.title || '—'}</div>
|
||||
<Show when={job.description}>
|
||||
<div style="font-size:12px;color:#64748b;margin-top:2px;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
||||
{job.description}
|
||||
</div>
|
||||
</Show>
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
<td class="text-slate-500">
|
||||
{job.required_skills?.join(', ') || job.experience_level || '—'}
|
||||
</td>
|
||||
<td style="color:#475569">{job.client_name || job.company_name || '—'}</td>
|
||||
<td style="color:#475569">
|
||||
<td class="text-slate-500">{job.client_name || job.company_name || '—'}</td>
|
||||
<td class="text-slate-500">
|
||||
{job.hourly_rate_min != null
|
||||
? `₹${job.hourly_rate_min}–₹${job.hourly_rate_max ?? job.hourly_rate_min}/hr`
|
||||
: '—'}
|
||||
</td>
|
||||
<td style="color:#475569">{job.location || '—'}</td>
|
||||
<td class="text-slate-500">{job.location || '—'}</td>
|
||||
<td>
|
||||
<span class={statusChipClass(job.status)}>{job.status || '—'}</span>
|
||||
</td>
|
||||
|
|
@ -151,7 +151,9 @@ export default function JobsPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,13 +49,13 @@ export default function LeadsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Leads Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View all requirements and lead requests from customers.</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Leads Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">View all requirements and lead requests from customers.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
{/* Filters */}
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px;align-items:center;">
|
||||
<input
|
||||
|
|
@ -92,9 +92,9 @@ export default function LeadsPage() {
|
|||
</Show>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
|
|
@ -118,20 +118,20 @@ export default function LeadsPage() {
|
|||
<Show when={!leads.loading && !leads.error && filtered().length > 0}>
|
||||
<For each={filtered()}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td>
|
||||
<div style="font-weight:600;color:#0f172a">{item.title || '—'}</div>
|
||||
<div class="font-semibold text-slate-900">{item.title || '—'}</div>
|
||||
<Show when={item.description}>
|
||||
<div style="font-size:12px;color:#64748b;margin-top:2px">
|
||||
{String(item.description).slice(0, 60)}{String(item.description).length > 60 ? '…' : ''}
|
||||
</div>
|
||||
</Show>
|
||||
</td>
|
||||
<td style="color:#475569">{item.profession || item.role || '—'}</td>
|
||||
<td style="color:#475569">
|
||||
<td class="text-slate-500">{item.profession || item.role || '—'}</td>
|
||||
<td class="text-slate-500">
|
||||
{item.budget_range || (item.budget_min != null ? `₹${item.budget_min}–₹${item.budget_max}` : '—')}
|
||||
</td>
|
||||
<td style="color:#475569">{item.location || '—'}</td>
|
||||
<td class="text-slate-500">{item.location || '—'}</td>
|
||||
<td>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${(item.status === 'ACTIVE' || item.status === 'OPEN') ? 'active' : ''}`}>
|
||||
{item.status || '—'}
|
||||
|
|
@ -149,7 +149,9 @@ export default function LeadsPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,16 +56,16 @@ export default function LedgerPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Ledger Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Platform financial ledger</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Ledger Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Platform financial ledger</p>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="flex-1 p-6">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
|
|
@ -92,18 +92,18 @@ export default function LedgerPage() {
|
|||
{(item) => {
|
||||
const entryType = item.entry_type || item.type || '—';
|
||||
return (
|
||||
<tr>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td>
|
||||
<span style={`${typeBadgeStyle(entryType)};padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;border:1px solid;display:inline-block`}>
|
||||
{entryType}
|
||||
</span>
|
||||
</td>
|
||||
<td style="color:#475569;font-size:12px;font-family:monospace">{item.order_id || '—'}</td>
|
||||
<td style="color:#475569;font-size:12px;font-family:monospace">{item.invoice_id || '—'}</td>
|
||||
<td style="color:#475569;font-size:12px;font-family:monospace">{item.user_id || '—'}</td>
|
||||
<td style="font-weight:600;color:#0f172a">{formatAmount(item)}</td>
|
||||
<td style="color:#475569">{item.note || '—'}</td>
|
||||
<td style="color:#475569">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||||
<td class="text-slate-500" style="font-size:12px;font-family:monospace">{item.order_id || '—'}</td>
|
||||
<td class="text-slate-500" style="font-size:12px;font-family:monospace">{item.invoice_id || '—'}</td>
|
||||
<td class="text-slate-500" style="font-size:12px;font-family:monospace">{item.user_id || '—'}</td>
|
||||
<td class="font-semibold text-slate-900">{formatAmount(item)}</td>
|
||||
<td class="text-slate-500">{item.note || '—'}</td>
|
||||
<td class="text-slate-500">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||||
</tr>
|
||||
);
|
||||
}}
|
||||
|
|
@ -112,7 +112,9 @@ export default function LedgerPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,10 +83,11 @@ export default function NotificationsPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Notifications</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Approval outcomes and action-required updates</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Notifications</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Approval outcomes and action-required updates</p>
|
||||
</div>
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
|
|
@ -98,26 +99,31 @@ export default function NotificationsPage() {
|
|||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style="display:flex;border-bottom:2px solid #e2e8f0;margin-bottom:24px;gap:0;overflow-x:auto;">
|
||||
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'all' ? ' active' : ''}`}
|
||||
class={activeTab() === 'all'
|
||||
? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium'
|
||||
: 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab('all')}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'unread' ? ' active' : ''}`}
|
||||
class={activeTab() === 'unread'
|
||||
? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium'
|
||||
: 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab('unread')}
|
||||
>
|
||||
Unread ({unreadCount()})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="flex-1 p-6">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
|
|
@ -136,15 +142,15 @@ export default function NotificationsPage() {
|
|||
</Show>
|
||||
<For each={visibleRows()}>
|
||||
{(item) => (
|
||||
<tr style={item.read_at === null ? 'background:#eff6ff' : ''}>
|
||||
<td style="font-weight:600;color:#0f172a;min-width:160px">{item.title}</td>
|
||||
<td style="font-size:13px;color:#475569;max-width:320px">{truncate(item.message, 80)}</td>
|
||||
<tr class="hover:bg-slate-50" style={item.read_at === null ? 'background:#eff6ff' : ''}>
|
||||
<td class="font-semibold text-slate-900" style="min-width:160px">{item.title}</td>
|
||||
<td class="text-slate-500" style="max-width:320px">{truncate(item.message, 80)}</td>
|
||||
<td>
|
||||
<span style={`${BADGE_STYLE};${eventTypeBadgeStyle(item.event_type)}`}>
|
||||
{item.event_type}
|
||||
</span>
|
||||
</td>
|
||||
<td style="font-size:12px;color:#64748b;white-space:nowrap">
|
||||
<td class="text-slate-500" style="font-size:12px;white-space:nowrap">
|
||||
{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
|
|
@ -166,7 +172,7 @@ export default function NotificationsPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<Show when={loading() && rows().length > 0}>
|
||||
<p style="text-align:center;padding:12px;font-size:13px;color:#64748b">Loading...</p>
|
||||
|
|
@ -182,6 +188,8 @@ export default function NotificationsPage() {
|
|||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,26 +70,27 @@ export default function OrderPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Order Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">TraceCoin package purchase orders</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Order Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">TraceCoin package purchase orders</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<div style="margin-bottom:16px">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by order number, user email, or status..."
|
||||
value={search()}
|
||||
onInput={(e) => setSearch(e.currentTarget.value)}
|
||||
style="width:100%;max-width:420px;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px"
|
||||
class="rounded-lg border border-gray-200 px-3 py-2 text-sm"
|
||||
style="width:100%;max-width:420px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order #</th>
|
||||
|
|
@ -115,13 +116,13 @@ export default function OrderPage() {
|
|||
<Show when={!orders.loading && !orders.error && filtered().length > 0}>
|
||||
<For each={filtered()}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a;font-family:monospace">{item.order_number || item.id}</td>
|
||||
<td style="color:#475569">{item.user_name || item.user_email || '—'}</td>
|
||||
<td style="color:#475569">{item.package_name || '—'}</td>
|
||||
<td style="color:#475569">{item.tracecoin_amount ?? '—'}</td>
|
||||
<td style="color:#475569">{item.coupon_code || '—'}</td>
|
||||
<td style="color:#475569">{formatAmount(item)}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900" style="font-family:monospace">{item.order_number || item.id}</td>
|
||||
<td class="text-slate-500">{item.user_name || item.user_email || '—'}</td>
|
||||
<td class="text-slate-500">{item.package_name || '—'}</td>
|
||||
<td class="text-slate-500">{item.tracecoin_amount ?? '—'}</td>
|
||||
<td class="text-slate-500">{item.coupon_code || '—'}</td>
|
||||
<td class="text-slate-500">{formatAmount(item)}</td>
|
||||
<td>
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"
|
||||
|
|
@ -130,7 +131,7 @@ export default function OrderPage() {
|
|||
{item.status || '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td style="color:#475569">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||||
<td class="text-slate-500">{item.created_at ? new Date(item.created_at).toLocaleString() : '—'}</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -138,7 +139,9 @@ export default function OrderPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,36 +150,36 @@ export default function PricingPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Pricing Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Create and manage TraceCoin packages</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Pricing Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Create and manage TraceCoin packages</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style="display:flex;border-bottom:2px solid #e2e8f0;margin-bottom:24px;gap:0;overflow-x:auto;">
|
||||
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${view() === 'packages' ? ' active' : ''}`}
|
||||
class={view() === 'packages' ? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium' : 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setView('packages')}
|
||||
>
|
||||
Packages
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${view() === 'create' ? ' active' : ''}`}
|
||||
class={view() === 'create' ? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium' : 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setView('create')}
|
||||
>
|
||||
Create Package
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
{/* Packages list tab */}
|
||||
<Show when={view() === 'packages'}>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
|
|
@ -205,12 +205,12 @@ export default function PricingPage() {
|
|||
<For each={packages()}>
|
||||
{(pkg) => (
|
||||
<>
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{pkg.name}</td>
|
||||
<td style="color:#475569">{pkg.role}</td>
|
||||
<td style="color:#475569">{pkg.tracecoin_amount}</td>
|
||||
<td style="color:#475569">₹{(pkg.price_inr / 100).toFixed(2)}</td>
|
||||
<td style="color:#475569">{pkg.bonus_percentage != null ? `${pkg.bonus_percentage}%` : '—'}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900">{pkg.name}</td>
|
||||
<td class="text-slate-500">{pkg.role}</td>
|
||||
<td class="text-slate-500">{pkg.tracecoin_amount}</td>
|
||||
<td class="text-slate-500">₹{(pkg.price_inr / 100).toFixed(2)}</td>
|
||||
<td class="text-slate-500">{pkg.bonus_percentage != null ? `${pkg.bonus_percentage}%` : '—'}</td>
|
||||
<td>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${pkg.is_active ? 'active' : ''}`}>
|
||||
{pkg.is_active ? 'Active' : 'Inactive'}
|
||||
|
|
@ -220,7 +220,7 @@ export default function PricingPage() {
|
|||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => startEdit(pkg)}>Edit</button>
|
||||
<button
|
||||
class={pkg.is_active ? 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors' : 'inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors'}
|
||||
class={pkg.is_active ? 'inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors' : 'inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors'}
|
||||
disabled={togglingId() === pkg.id}
|
||||
onClick={() => toggleActive(pkg)}
|
||||
>
|
||||
|
|
@ -264,7 +264,7 @@ export default function PricingPage() {
|
|||
/>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={editSaving()} onClick={() => saveEdit(pkg.id)}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" disabled={editSaving()} onClick={() => saveEdit(pkg.id)}>
|
||||
{editSaving() ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={cancelEdit}>Cancel</button>
|
||||
|
|
@ -280,7 +280,7 @@ export default function PricingPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Create Package tab */}
|
||||
|
|
@ -349,13 +349,15 @@ export default function PricingPage() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={cSaving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={cSaving()}>
|
||||
{cSaving() ? 'Creating...' : 'Create Package'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,13 @@ export default function ReportPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Report Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View platform analytics and generate reports.</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Report Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">View platform analytics and generate reports.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700">Date Range</h2>
|
||||
<form onSubmit={handleLoad} style="display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap">
|
||||
|
|
@ -76,7 +76,7 @@ export default function ReportPage() {
|
|||
style="padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px"
|
||||
/>
|
||||
</div>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={loading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={loading()}>
|
||||
{loading() ? 'Loading...' : 'Load Report'}
|
||||
</button>
|
||||
</form>
|
||||
|
|
@ -115,6 +115,8 @@ export default function ReportPage() {
|
|||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,31 +120,35 @@ export default function ReviewPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Review Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Moderate platform reviews</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Review Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Moderate platform reviews</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style="display:flex;border-bottom:2px solid #e2e8f0;margin-bottom:24px;gap:0;overflow-x:auto;">
|
||||
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'list' ? ' active' : ''}`}
|
||||
class={activeTab() === 'list'
|
||||
? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium'
|
||||
: 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab('list')}
|
||||
>
|
||||
Reviews
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'create' ? ' active' : ''}`}
|
||||
class={activeTab() === 'create'
|
||||
? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium'
|
||||
: 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => { resetForm(); setActiveTab('create'); }}
|
||||
>
|
||||
Create Review
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<Show when={activeTab() === 'list'}>
|
||||
<div style="margin-bottom:16px">
|
||||
<input
|
||||
|
|
@ -155,9 +159,9 @@ export default function ReviewPage() {
|
|||
style="padding:8px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;min-width:320px"
|
||||
/>
|
||||
</div>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Reviewer</th>
|
||||
|
|
@ -184,17 +188,17 @@ export default function ReviewPage() {
|
|||
const subjectType = item.subject_type || '—';
|
||||
const isPublished = item.status === 'PUBLISHED';
|
||||
return (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{item.reviewer_name || item.reviewer_id || '—'}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900">{item.reviewer_name || item.reviewer_id || '—'}</td>
|
||||
<td>
|
||||
<span style={`${typeBadgeStyle(subjectType)};padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;border:1px solid;display:inline-block`}>
|
||||
{subjectType}
|
||||
</span>
|
||||
</td>
|
||||
<td style="color:#475569">
|
||||
<td class="text-slate-500">
|
||||
{item.rating != null ? `⭐ ${item.rating}` : '—'}
|
||||
</td>
|
||||
<td style="color:#475569">{item.title || '—'}</td>
|
||||
<td class="text-slate-500">{item.title || '—'}</td>
|
||||
<td>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${isPublished ? 'active' : ''}`}
|
||||
style={isPublished ? '' : 'background:#f1f5f9;color:#475569;border-color:#e2e8f0'}
|
||||
|
|
@ -215,7 +219,7 @@ export default function ReviewPage() {
|
|||
</Show>
|
||||
<Show when={!isPublished}>
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors"
|
||||
class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors"
|
||||
disabled={toggling() === item.id}
|
||||
onClick={() => handleUpdateStatus(item, 'PUBLISHED')}
|
||||
>
|
||||
|
|
@ -232,7 +236,7 @@ export default function ReviewPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={activeTab() === 'create'}>
|
||||
|
|
@ -306,13 +310,15 @@ export default function ReviewPage() {
|
|||
/>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Review'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,13 +158,35 @@ export default function SupportPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Support Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Handle platform issues and customer queries</p>
|
||||
</div>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Support Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Handle platform issues and customer queries</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
|
||||
<button
|
||||
type="button"
|
||||
class={activeTab() === 'queue'
|
||||
? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium'
|
||||
: 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab('queue')}
|
||||
>
|
||||
Support Queue
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={activeTab() === 'create'
|
||||
? 'py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium'
|
||||
: 'py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors'}
|
||||
onClick={() => setActiveTab('create')}
|
||||
>
|
||||
Create Case
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
{/* Stats bar */}
|
||||
<div style="display:flex;gap:12px;margin-bottom:16px">
|
||||
<For each={statCards}>
|
||||
|
|
@ -177,24 +199,6 @@ export default function SupportPage() {
|
|||
</For>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style="display:flex;border-bottom:2px solid #e2e8f0;margin-bottom:24px;gap:0;overflow-x:auto;">
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'queue' ? ' active' : ''}`}
|
||||
onClick={() => setActiveTab('queue')}
|
||||
>
|
||||
Support Queue
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`admin-tab${activeTab() === 'create' ? ' active' : ''}`}
|
||||
onClick={() => setActiveTab('create')}
|
||||
>
|
||||
Create Case
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Support Queue Tab */}
|
||||
<Show when={activeTab() === 'queue'}>
|
||||
<div style="display:flex;flex-direction:column;gap:16px">
|
||||
|
|
@ -211,9 +215,9 @@ export default function SupportPage() {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Issue</th>
|
||||
|
|
@ -238,9 +242,9 @@ export default function SupportPage() {
|
|||
<Show when={!cases.loading && !cases.error && filteredCases().length > 0}>
|
||||
<For each={filteredCases()}>
|
||||
{(item) => (
|
||||
<tr style="cursor:pointer" onClick={() => {}}>
|
||||
<tr class="hover:bg-slate-50" style="cursor:pointer" onClick={() => {}}>
|
||||
<td>
|
||||
<div style="font-weight:600;color:#0f172a">{item.title}</div>
|
||||
<div class="font-semibold text-slate-900">{item.title}</div>
|
||||
<div style="font-size:12px;color:#64748b;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{item.description}</div>
|
||||
</td>
|
||||
<td>
|
||||
|
|
@ -256,7 +260,7 @@ export default function SupportPage() {
|
|||
<div style="font-size:13px">{item.requesterName || '—'}</div>
|
||||
<div style="font-size:11px;color:#64748b">{item.requesterEmail || ''}</div>
|
||||
</td>
|
||||
<td style="font-size:12px;color:#64748b">
|
||||
<td class="text-slate-500" style="font-size:12px">
|
||||
{item.updatedAt ? new Date(item.updatedAt).toLocaleString() : '—'}
|
||||
</td>
|
||||
<td>
|
||||
|
|
@ -271,7 +275,7 @@ export default function SupportPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
|
|
@ -358,13 +362,15 @@ export default function SupportPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={createLoading()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={createLoading()}>
|
||||
{createLoading() ? 'Creating...' : 'Create Support Case'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,16 +64,18 @@ export default function TaxPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Tax Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Configure tax rates for platform transactions.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Tax Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Configure tax rates for platform transactions.</p>
|
||||
</div>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowForm(!showForm())}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" onClick={() => setShowForm(!showForm())}>
|
||||
{showForm() ? 'Cancel' : 'Add Tax'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6">
|
||||
<Show when={showForm()}>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="margin-bottom:16px">
|
||||
<h2 style="margin:0 0 16px;font-size:16px;font-weight:700">New Tax</h2>
|
||||
|
|
@ -114,7 +116,7 @@ export default function TaxPage() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
|
||||
<button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-semibold text-white hover:bg-[#0f2a4e] transition-colors" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Tax'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -122,9 +124,9 @@ export default function TaxPage() {
|
|||
</section>
|
||||
</Show>
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
|
|
@ -146,10 +148,10 @@ export default function TaxPage() {
|
|||
</Show>
|
||||
<Show when={!taxes.loading && !taxes.error && (taxes()?.length ?? 0) > 0}>
|
||||
{taxes()!.map((item) => (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{item.name}</td>
|
||||
<td style="color:#475569">{item.rate}%</td>
|
||||
<td style="color:#475569">{item.description || '—'}</td>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-semibold text-slate-900">{item.name}</td>
|
||||
<td class="text-slate-500">{item.rate}%</td>
|
||||
<td class="text-slate-500">{item.description || '—'}</td>
|
||||
<td>
|
||||
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${item.is_active !== false ? 'active' : ''}`}>
|
||||
{item.is_active !== false ? 'Active' : 'Inactive'}
|
||||
|
|
@ -173,7 +175,9 @@ export default function TaxPage() {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue