feat: modularize admin settings into individual components and standardize swapstation role naming
This commit is contained in:
@@ -39,7 +39,7 @@ const buildDefaultGroups = (): PermissionGroup[] => [
|
||||
{
|
||||
id: 'kyc',
|
||||
title: 'KYC Requests & Verification',
|
||||
description: 'The Biker user will request from the app. Investor, Shop, Merchant will request from the website. Front desk officers (hub/head office) can request for Biker, Investor, Shop, Merchant and can upload remaining documents. Admin officers (head office) will approve or reject documents with notes and "make a Biker | Investor | Shop | Merchant".',
|
||||
description: 'The Biker user will request from the app. Investor, Swap Station, Merchant will request from the website. Front desk officers (hub/head office) can request for Biker, Investor, Swap Station, Merchant and can upload remaining documents. Admin officers (head office) will approve or reject documents with notes and "make a Biker | Investor | Swap Station | Merchant".',
|
||||
icon: FileSearch,
|
||||
permissions: [
|
||||
{ key: 'kyc.request', label: 'KYC Request', enabled: false },
|
||||
@@ -47,7 +47,7 @@ const buildDefaultGroups = (): PermissionGroup[] => [
|
||||
{ key: 'kyc.doc_upload', label: 'Document Upload', enabled: false },
|
||||
{ key: 'kyc.doc_approve', label: 'Document Approve', enabled: false },
|
||||
{ key: 'kyc.doc_reject', label: 'Document Reject', enabled: false },
|
||||
{ key: 'kyc.make_valid_user', label: 'Make a Biker | Make an Investor | Make a Shop | Make a Merchant', enabled: false },
|
||||
{ key: 'kyc.make_valid_user', label: 'Make a Biker | Make an Investor | Make a Swap Station | Make a Merchant', enabled: false },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
119
src/app/admin/settings/components/BrandingSettings.tsx
Normal file
119
src/app/admin/settings/components/BrandingSettings.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
'use client';
|
||||
|
||||
import { Image, Camera, Ruler } from 'lucide-react';
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface BrandingSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
}
|
||||
|
||||
export default function BrandingSettings({ settings, setSettings }: BrandingSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Branding & Design</h3>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Company Logo</label>
|
||||
<div className="mt-2 border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-accent cursor-pointer">
|
||||
<Image className="w-12 h-12 mx-auto text-slate-400" />
|
||||
<p className="text-sm text-slate-500 mt-2">Upload Logo (PNG, SVG)</p>
|
||||
<p className="text-xs text-slate-400">Recommended: 200x50px</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Favicon</label>
|
||||
<div className="mt-2 border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-accent cursor-pointer">
|
||||
<Camera className="w-12 h-12 mx-auto text-slate-400" />
|
||||
<p className="text-sm text-slate-500 mt-2">Upload Favicon</p>
|
||||
<p className="text-xs text-slate-400">Recommended: 32x32px</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-6 pt-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Primary Color</label>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
<input
|
||||
type="color"
|
||||
value={settings.primaryColor}
|
||||
onChange={(e) => setSettings({ ...settings, primaryColor: e.target.value })}
|
||||
className="w-12 h-12 rounded-lg cursor-pointer border-0"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.primaryColor}
|
||||
onChange={(e) => setSettings({ ...settings, primaryColor: e.target.value })}
|
||||
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Secondary Color</label>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
<input
|
||||
type="color"
|
||||
value={settings.secondaryColor}
|
||||
onChange={(e) => setSettings({ ...settings, secondaryColor: e.target.value })}
|
||||
className="w-12 h-12 rounded-lg cursor-pointer border-0"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.secondaryColor}
|
||||
onChange={(e) => setSettings({ ...settings, secondaryColor: e.target.value })}
|
||||
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Border Radius</label>
|
||||
<div className="flex items-center gap-4 mt-2">
|
||||
<Ruler className="w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={20}
|
||||
value={settings.borderRadius}
|
||||
onChange={(e) => setSettings({ ...settings, borderRadius: parseInt(e.target.value) })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm text-slate-600">{settings.borderRadius}px</span>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
{[4, 8, 12, 16].map(radius => (
|
||||
<button
|
||||
key={radius}
|
||||
onClick={() => setSettings({ ...settings, borderRadius: radius })}
|
||||
className={`px-3 py-2 text-sm border ${settings.borderRadius === radius ? 'bg-accent text-white' : 'border-slate-200'}`}
|
||||
style={{ borderRadius: radius }}
|
||||
>
|
||||
{radius}px
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Preview</label>
|
||||
<div className="mt-2 p-6 bg-slate-100 rounded-lg flex items-center justify-center gap-4">
|
||||
<div
|
||||
className="px-4 py-2 text-white font-bold"
|
||||
style={{ backgroundColor: settings.primaryColor, borderRadius: settings.borderRadius }}
|
||||
>
|
||||
Primary
|
||||
</div>
|
||||
<div
|
||||
className="px-4 py-2 text-white font-bold"
|
||||
style={{ backgroundColor: settings.secondaryColor, borderRadius: settings.borderRadius }}
|
||||
>
|
||||
Secondary
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
349
src/app/admin/settings/components/CompanyPolicySettings.tsx
Normal file
349
src/app/admin/settings/components/CompanyPolicySettings.tsx
Normal file
@@ -0,0 +1,349 @@
|
||||
'use client';
|
||||
|
||||
import { Plus, Pencil, Trash2 } from 'lucide-react';
|
||||
import RichTextEditor from '@/components/RichTextEditor';
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface CompanyPolicySettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
activeMasterTab: 'investor' | 'merchant' | 'swapstation' | 'rentalType';
|
||||
setActiveMasterTab: (tab: 'investor' | 'merchant' | 'swapstation' | 'rentalType') => void;
|
||||
activeRentalTypeTab: 'single' | 'shared' | 'renttoown';
|
||||
setActiveRentalTypeTab: (tab: 'single' | 'shared' | 'renttoown') => void;
|
||||
editingPolicy: { tab: string; index: number } | null;
|
||||
setEditingPolicy: (policy: { tab: string; index: number } | null) => void;
|
||||
editPolicyName: string;
|
||||
setEditPolicyName: (name: string) => void;
|
||||
editPolicyDesc: string;
|
||||
setEditPolicyDesc: (desc: string) => void;
|
||||
editPolicyDescHtml: string;
|
||||
setEditPolicyDescHtml: (desc: string) => void;
|
||||
showAddPolicy: boolean;
|
||||
setShowAddPolicy: (show: boolean) => void;
|
||||
newPolicyName: string;
|
||||
setNewPolicyName: (name: string) => void;
|
||||
newPolicyDesc: string;
|
||||
setNewPolicyDesc: (desc: string) => void;
|
||||
}
|
||||
|
||||
export default function CompanyPolicySettings({
|
||||
settings,
|
||||
setSettings,
|
||||
activeMasterTab,
|
||||
setActiveMasterTab,
|
||||
activeRentalTypeTab,
|
||||
setActiveRentalTypeTab,
|
||||
editingPolicy,
|
||||
setEditingPolicy,
|
||||
editPolicyName,
|
||||
setEditPolicyName,
|
||||
editPolicyDesc,
|
||||
setEditPolicyDesc,
|
||||
editPolicyDescHtml,
|
||||
setEditPolicyDescHtml,
|
||||
showAddPolicy,
|
||||
setShowAddPolicy,
|
||||
newPolicyName,
|
||||
setNewPolicyName,
|
||||
newPolicyDesc,
|
||||
setNewPolicyDesc,
|
||||
}: CompanyPolicySettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Company's Policy</h3>
|
||||
|
||||
<div className="flex gap-2 border-b border-slate-200">
|
||||
<button onClick={() => setActiveMasterTab('investor')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeMasterTab === 'investor' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Investor</button>
|
||||
<button onClick={() => setActiveMasterTab('merchant')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeMasterTab === 'merchant' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Merchant</button>
|
||||
<button onClick={() => setActiveMasterTab('swapstation')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeMasterTab === 'swapstation' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Swap Station</button>
|
||||
<button onClick={() => setActiveMasterTab('rentalType')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeMasterTab === 'rentalType' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Rental Types</button>
|
||||
</div>
|
||||
|
||||
{activeMasterTab === 'investor' && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm text-slate-600 font-medium">Policy List</label>
|
||||
<button onClick={() => setShowAddPolicy(true)} className="text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1">
|
||||
<Plus className="w-3 h-3" /> Add Policy
|
||||
</button>
|
||||
</div>
|
||||
{showAddPolicy && (
|
||||
<div className="p-3 bg-white rounded-lg border border-blue-200">
|
||||
<input type="text" value={newPolicyName} onChange={(e) => setNewPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm mb-2" placeholder="Policy Title" />
|
||||
<RichTextEditor value={newPolicyDesc} onChange={(val) => setNewPolicyDesc(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2 mt-2">
|
||||
<button onClick={() => {
|
||||
if (!newPolicyName.trim()) return;
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: [...settings.companyPolicy.investor, { title: newPolicyName, description: newPolicyDesc }] } });
|
||||
setNewPolicyName('');
|
||||
setNewPolicyDesc('');
|
||||
setShowAddPolicy(false);
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Add</button>
|
||||
<button onClick={() => { setShowAddPolicy(false); setNewPolicyName(''); setNewPolicyDesc(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
{(settings.companyPolicy?.investor || []).map((policy, i) => (
|
||||
<div key={i} className="p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
{editingPolicy?.tab === 'investor' && editingPolicy?.index === i ? (
|
||||
<div className="space-y-2">
|
||||
<input type="text" value={editPolicyName} onChange={(e) => setEditPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Policy Title" />
|
||||
<RichTextEditor value={editPolicyDescHtml} onChange={(val) => setEditPolicyDescHtml(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => {
|
||||
const updated = [...settings.companyPolicy.investor];
|
||||
updated[i] = { title: editPolicyName, description: editPolicyDescHtml };
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: updated } });
|
||||
setEditingPolicy(null);
|
||||
setEditPolicyDescHtml('');
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Save</button>
|
||||
<button onClick={() => { setEditingPolicy(null); setEditPolicyDescHtml(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-700">{policy.title}</span>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 mt-1 prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: policy.description }} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
<button onClick={() => { setEditingPolicy({ tab: 'investor', index: i }); setEditPolicyName(policy.title); setEditPolicyDescHtml(policy.description); }} className="p-1 text-slate-400 hover:text-blue-600">
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => {
|
||||
const updated = settings.companyPolicy.investor.filter((_, idx) => idx !== i);
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: updated } });
|
||||
}} className="p-1 text-slate-400 hover:text-red-600">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeMasterTab === 'merchant' && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm text-slate-600 font-medium">Policy List</label>
|
||||
<button onClick={() => setShowAddPolicy(true)} className="text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1">
|
||||
<Plus className="w-3 h-3" /> Add Policy
|
||||
</button>
|
||||
</div>
|
||||
{showAddPolicy && (
|
||||
<div className="p-3 bg-white rounded-lg border border-blue-200">
|
||||
<input type="text" value={newPolicyName} onChange={(e) => setNewPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm mb-2" placeholder="Policy Title" />
|
||||
<RichTextEditor value={newPolicyDesc} onChange={(val) => setNewPolicyDesc(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2 mt-2">
|
||||
<button onClick={() => {
|
||||
if (!newPolicyName.trim()) return;
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: [...settings.companyPolicy.merchant, { title: newPolicyName, description: newPolicyDesc }] } });
|
||||
setNewPolicyName('');
|
||||
setNewPolicyDesc('');
|
||||
setShowAddPolicy(false);
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Add</button>
|
||||
<button onClick={() => { setShowAddPolicy(false); setNewPolicyName(''); setNewPolicyDesc(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
{(settings.companyPolicy?.merchant || []).map((policy, i) => (
|
||||
<div key={i} className="p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
{editingPolicy?.tab === 'merchant' && editingPolicy?.index === i ? (
|
||||
<div className="space-y-2">
|
||||
<input type="text" value={editPolicyName} onChange={(e) => setEditPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Policy Title" />
|
||||
<RichTextEditor value={editPolicyDescHtml} onChange={(val) => setEditPolicyDescHtml(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => {
|
||||
const updated = [...settings.companyPolicy.merchant];
|
||||
updated[i] = { title: editPolicyName, description: editPolicyDescHtml };
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: updated } });
|
||||
setEditingPolicy(null);
|
||||
setEditPolicyDescHtml('');
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Save</button>
|
||||
<button onClick={() => { setEditingPolicy(null); setEditPolicyDescHtml(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-700">{policy.title}</span>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 mt-1 prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: policy.description }} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
<button onClick={() => { setEditingPolicy({ tab: 'merchant', index: i }); setEditPolicyName(policy.title); setEditPolicyDescHtml(policy.description); }} className="p-1 text-slate-400 hover:text-blue-600">
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => {
|
||||
const updated = settings.companyPolicy.merchant.filter((_, idx) => idx !== i);
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: updated } });
|
||||
}} className="p-1 text-slate-400 hover:text-red-600">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeMasterTab === 'swapstation' && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm text-slate-600 font-medium">Policy List</label>
|
||||
<button onClick={() => setShowAddPolicy(true)} className="text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1">
|
||||
<Plus className="w-3 h-3" /> Add Policy
|
||||
</button>
|
||||
</div>
|
||||
{showAddPolicy && (
|
||||
<div className="p-3 bg-white rounded-lg border border-blue-200">
|
||||
<input type="text" value={newPolicyName} onChange={(e) => setNewPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm mb-2" placeholder="Policy Title" />
|
||||
<RichTextEditor value={newPolicyDesc} onChange={(val) => setNewPolicyDesc(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2 mt-2">
|
||||
<button onClick={() => {
|
||||
if (!newPolicyName.trim()) return;
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: [...settings.companyPolicy.swapStation, { title: newPolicyName, description: newPolicyDesc }] } });
|
||||
setNewPolicyName('');
|
||||
setNewPolicyDesc('');
|
||||
setShowAddPolicy(false);
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Add</button>
|
||||
<button onClick={() => { setShowAddPolicy(false); setNewPolicyName(''); setNewPolicyDesc(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
{(settings.companyPolicy?.swapStation || []).map((policy, i) => (
|
||||
<div key={i} className="p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
{editingPolicy?.tab === 'swapstation' && editingPolicy?.index === i ? (
|
||||
<div className="space-y-2">
|
||||
<input type="text" value={editPolicyName} onChange={(e) => setEditPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Policy Title" />
|
||||
<RichTextEditor value={editPolicyDescHtml} onChange={(val) => setEditPolicyDescHtml(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => {
|
||||
const updated = [...settings.companyPolicy.swapStation];
|
||||
updated[i] = { title: editPolicyName, description: editPolicyDescHtml };
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: updated } });
|
||||
setEditingPolicy(null);
|
||||
setEditPolicyDescHtml('');
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Save</button>
|
||||
<button onClick={() => { setEditingPolicy(null); setEditPolicyDescHtml(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-700">{policy.title}</span>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 mt-1 prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: policy.description }} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
<button onClick={() => { setEditingPolicy({ tab: 'swapstation', index: i }); setEditPolicyName(policy.title); setEditPolicyDescHtml(policy.description); }} className="p-1 text-slate-400 hover:text-blue-600">
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => {
|
||||
const updated = settings.companyPolicy.swapStation.filter((_, idx) => idx !== i);
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: updated } });
|
||||
}} className="p-1 text-slate-400 hover:text-red-600">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeMasterTab === 'rentalType' && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2 border-b border-slate-200">
|
||||
<button onClick={() => setActiveRentalTypeTab('single')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeRentalTypeTab === 'single' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Rental (Single)</button>
|
||||
<button onClick={() => setActiveRentalTypeTab('shared')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeRentalTypeTab === 'shared' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Rental (2 Person Shared)</button>
|
||||
<button onClick={() => setActiveRentalTypeTab('renttoown')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeRentalTypeTab === 'renttoown' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Rent-to-Own</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm text-slate-600 font-medium">Policy List</label>
|
||||
<button onClick={() => setShowAddPolicy(true)} className="text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1">
|
||||
<Plus className="w-3 h-3" /> Add Policy
|
||||
</button>
|
||||
</div>
|
||||
{showAddPolicy && (
|
||||
<div className="p-3 bg-white rounded-lg border border-blue-200">
|
||||
<input type="text" value={newPolicyName} onChange={(e) => setNewPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm mb-2" placeholder="Policy Title" />
|
||||
<RichTextEditor value={newPolicyDesc} onChange={(val) => setNewPolicyDesc(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2 mt-2">
|
||||
<button onClick={() => {
|
||||
if (!newPolicyName.trim()) return;
|
||||
const updated = { ...settings.companyPolicy.rentalTypes };
|
||||
updated[activeRentalTypeTab] = [...updated[activeRentalTypeTab], { title: newPolicyName, description: newPolicyDesc }];
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, rentalTypes: updated } });
|
||||
setNewPolicyName('');
|
||||
setNewPolicyDesc('');
|
||||
setShowAddPolicy(false);
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Add</button>
|
||||
<button onClick={() => { setShowAddPolicy(false); setNewPolicyName(''); setNewPolicyDesc(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
{(settings.companyPolicy?.rentalTypes[activeRentalTypeTab] || []).map((policy, i) => (
|
||||
<div key={i} className="p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
{editingPolicy?.tab === 'rentalType' && editingPolicy?.index === i ? (
|
||||
<div className="space-y-2">
|
||||
<input type="text" value={editPolicyName} onChange={(e) => setEditPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Policy Title" />
|
||||
<RichTextEditor value={editPolicyDescHtml} onChange={(val) => setEditPolicyDescHtml(val)} placeholder="Policy Description..." minHeight={100} />
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => {
|
||||
const updated = { ...settings.companyPolicy.rentalTypes };
|
||||
updated[activeRentalTypeTab][i] = { title: editPolicyName, description: editPolicyDescHtml };
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, rentalTypes: updated } });
|
||||
setEditingPolicy(null);
|
||||
setEditPolicyDescHtml('');
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Save</button>
|
||||
<button onClick={() => { setEditingPolicy(null); setEditPolicyDescHtml(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-700">{policy.title}</span>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 mt-1 prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: policy.description }} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
<button onClick={() => { setEditingPolicy({ tab: 'rentalType', index: i }); setEditPolicyName(policy.title); setEditPolicyDescHtml(policy.description); }} className="p-1 text-slate-400 hover:text-blue-600">
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => {
|
||||
const updated = { ...settings.companyPolicy.rentalTypes };
|
||||
updated[activeRentalTypeTab] = updated[activeRentalTypeTab].filter((_, idx) => idx !== i);
|
||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, rentalTypes: updated } });
|
||||
}} className="p-1 text-slate-400 hover:text-red-600">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
171
src/app/admin/settings/components/GeneralSettings.tsx
Normal file
171
src/app/admin/settings/components/GeneralSettings.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
'use client';
|
||||
|
||||
import { Upload } from 'lucide-react';
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface GeneralSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
}
|
||||
|
||||
export default function GeneralSettings({ settings, setSettings }: GeneralSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">General Information</h3>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Company Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.name}
|
||||
onChange={(e) => setSettings({ ...settings, name: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Short Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.shortName}
|
||||
onChange={(e) => setSettings({ ...settings, shortName: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Tagline</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.tagLine}
|
||||
onChange={(e) => setSettings({ ...settings, tagLine: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Website</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.website}
|
||||
onChange={(e) => setSettings({ ...settings, website: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Description</label>
|
||||
<textarea
|
||||
value={settings.description}
|
||||
onChange={(e) => setSettings({ ...settings, description: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={settings.email}
|
||||
onChange={(e) => setSettings({ ...settings, email: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Phone</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.phone}
|
||||
onChange={(e) => setSettings({ ...settings, phone: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Address</label>
|
||||
<textarea
|
||||
value={settings.address}
|
||||
onChange={(e) => setSettings({ ...settings, address: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">City</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.city}
|
||||
onChange={(e) => setSettings({ ...settings, city: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Country</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.country}
|
||||
onChange={(e) => setSettings({ ...settings, country: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Timezone</label>
|
||||
<select
|
||||
value={settings.timezone}
|
||||
onChange={(e) => setSettings({ ...settings, timezone: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
>
|
||||
<option value="Asia/Dhaka">Asia/Dhaka (GMT+6)</option>
|
||||
<option value="Asia/Kolkata">Asia/Kolkata (GMT+5:30)</option>
|
||||
<option value="UTC">UTC (GMT+0)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold text-slate-800 pt-4 border-t">Business Registration</h3>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">TIN Number</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.tin}
|
||||
onChange={(e) => setSettings({ ...settings, tin: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">BIN Number</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.bin}
|
||||
onChange={(e) => setSettings({ ...settings, bin: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Trade License</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.tradeLicense}
|
||||
onChange={(e) => setSettings({ ...settings, tradeLicense: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Documents</label>
|
||||
<div className="mt-2 p-4 border-2 border-dashed border-slate-200 rounded-lg text-center hover:border-accent cursor-pointer">
|
||||
<Upload className="w-8 h-8 mx-auto text-slate-400" />
|
||||
<p className="text-sm text-slate-500 mt-1">Drop files here or click to upload</p>
|
||||
<p className="text-xs text-slate-400">PDF, JPG, PNG up to 10MB</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
135
src/app/admin/settings/components/IntegrationSettings.tsx
Normal file
135
src/app/admin/settings/components/IntegrationSettings.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
'use client';
|
||||
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface IntegrationSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
}
|
||||
|
||||
export default function IntegrationSettings({ settings, setSettings }: IntegrationSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Email Integration (SMTP)</h3>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">SMTP Host</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.smtp.host}
|
||||
onChange={(e) => setSettings({ ...settings, smtp: { ...settings.smtp, host: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">SMTP Port</label>
|
||||
<input
|
||||
type="number"
|
||||
value={settings.smtp.port}
|
||||
onChange={(e) => setSettings({ ...settings, smtp: { ...settings.smtp, port: parseInt(e.target.value) } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.smtp.user}
|
||||
onChange={(e) => setSettings({ ...settings, smtp: { ...settings.smtp, user: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={settings.smtp.password}
|
||||
onChange={(e) => setSettings({ ...settings, smtp: { ...settings.smtp, password: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">From Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={settings.smtp.fromEmail}
|
||||
onChange={(e) => setSettings({ ...settings, smtp: { ...settings.smtp, fromEmail: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">From Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.smtp.fromName}
|
||||
onChange={(e) => setSettings({ ...settings, smtp: { ...settings.smtp, fromName: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Encryption</label>
|
||||
<div className="flex gap-3 mt-2">
|
||||
{(['ssl', 'tls', 'none'] as const).map(enc => (
|
||||
<button
|
||||
key={enc}
|
||||
onClick={() => setSettings({ ...settings, smtp: { ...settings.smtp, encryption: enc } })}
|
||||
className={`px-4 py-2 text-sm rounded-lg border ${settings.smtp.encryption === enc
|
||||
? 'bg-accent text-white border-accent'
|
||||
: 'border-slate-200 text-slate-600'
|
||||
}`}
|
||||
>
|
||||
{enc.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold text-slate-800 pt-4 border-t">SMS Integration</h3>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Provider</label>
|
||||
<select
|
||||
value={settings.sms.provider}
|
||||
onChange={(e) => setSettings({ ...settings, sms: { ...settings.sms, provider: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
>
|
||||
<option value="BulkSMS">BulkSMS BD</option>
|
||||
<option value="Twilio">Twilio</option>
|
||||
<option value="Msg91">Msg91</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Sender ID</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.sms.senderId}
|
||||
onChange={(e) => setSettings({ ...settings, sms: { ...settings.sms, senderId: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div className="lg:col-span-2">
|
||||
<label className="text-sm text-slate-600">API Key</label>
|
||||
<input
|
||||
type="password"
|
||||
value={settings.sms.apiKey}
|
||||
onChange={(e) => setSettings({ ...settings, sms: { ...settings.sms, apiKey: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div className="lg:col-span-2">
|
||||
<label className="text-sm text-slate-600">API URL</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.sms.url}
|
||||
onChange={(e) => setSettings({ ...settings, sms: { ...settings.sms, url: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
274
src/app/admin/settings/components/InvestmentSettings.tsx
Normal file
274
src/app/admin/settings/components/InvestmentSettings.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
'use client';
|
||||
|
||||
import { Plus, X, Save } from 'lucide-react';
|
||||
import type { CompanySettings } from '../page';
|
||||
|
||||
interface InvestmentSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
activeInvestTab: number;
|
||||
setActiveInvestTab: (n: number) => void;
|
||||
addInvestPlan: boolean;
|
||||
setAddInvestPlan: (v: boolean) => void;
|
||||
newInvestName: string;
|
||||
setNewInvestName: (v: string) => void;
|
||||
newInvestStatus: string;
|
||||
setNewInvestStatus: (v: string) => void;
|
||||
newInvestTarget: number;
|
||||
setNewInvestTarget: (n: number) => void;
|
||||
newInvestStart: string;
|
||||
setNewInvestStart: (v: string) => void;
|
||||
newInvestEnd: string;
|
||||
setNewInvestEnd: (v: string) => void;
|
||||
newInvestMin: number;
|
||||
setNewInvestMin: (n: number) => void;
|
||||
newInvestMax: number;
|
||||
setNewInvestMax: (n: number) => void;
|
||||
newInvestDuration: number;
|
||||
setNewInvestDuration: (n: number) => void;
|
||||
newInvestLock: number;
|
||||
setNewInvestLock: (n: number) => void;
|
||||
newInvestPenalty: number;
|
||||
setNewInvestPenalty: (n: number) => void;
|
||||
newInvestMonthly: number;
|
||||
setNewInvestMonthly: (n: number) => void;
|
||||
newInvestTotal: number;
|
||||
setNewInvestTotal: (n: number) => void;
|
||||
newInvestFicoSingleRent: number;
|
||||
setNewInvestFicoSingleRent: (n: number) => void;
|
||||
newInvestFicoRentToOwn: number;
|
||||
setNewInvestFicoRentToOwn: (n: number) => void;
|
||||
newInvestFicoShareEv: number;
|
||||
setNewInvestFicoShareEv: (n: number) => void;
|
||||
newInvestDesc: string;
|
||||
setNewInvestDesc: (v: string) => void;
|
||||
createInvestPlan: () => void;
|
||||
handleSave: () => void;
|
||||
}
|
||||
|
||||
export default function InvestmentSettings({
|
||||
settings, setSettings,
|
||||
activeInvestTab, setActiveInvestTab,
|
||||
addInvestPlan, setAddInvestPlan,
|
||||
newInvestName, setNewInvestName,
|
||||
newInvestStatus, setNewInvestStatus,
|
||||
newInvestTarget, setNewInvestTarget,
|
||||
newInvestStart, setNewInvestStart,
|
||||
newInvestEnd, setNewInvestEnd,
|
||||
newInvestMin, setNewInvestMin,
|
||||
newInvestMax, setNewInvestMax,
|
||||
newInvestDuration, setNewInvestDuration,
|
||||
newInvestLock, setNewInvestLock,
|
||||
newInvestPenalty, setNewInvestPenalty,
|
||||
newInvestMonthly, setNewInvestMonthly,
|
||||
newInvestTotal, setNewInvestTotal,
|
||||
newInvestFicoSingleRent, setNewInvestFicoSingleRent,
|
||||
newInvestFicoRentToOwn, setNewInvestFicoRentToOwn,
|
||||
newInvestFicoShareEv, setNewInvestFicoShareEv,
|
||||
newInvestDesc, setNewInvestDesc,
|
||||
createInvestPlan, handleSave,
|
||||
}: InvestmentSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Investment Plans</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between bg-amber-50 border border-amber-200 rounded-xl p-4">
|
||||
<div>
|
||||
<h4 className="font-semibold text-amber-800">Investment Plans ({settings.plans.investment.length})</h4>
|
||||
<p className="text-sm text-amber-600">Manage investment plans for investors</p>
|
||||
</div>
|
||||
<button onClick={() => { setAddInvestPlan(true); setNewInvestName(''); }} className="px-4 py-2 bg-amber-600 text-white rounded-lg text-sm font-medium flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> New Plan
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{addInvestPlan && (
|
||||
<div className="bg-white rounded-xl border border-amber-300 overflow-hidden">
|
||||
<div className="bg-amber-100 px-4 py-3 border-b border-amber-200 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-amber-800">New Investment Plan</h4>
|
||||
<p className="text-sm text-amber-600 mt-1">Fill in the details below</p>
|
||||
</div>
|
||||
<button onClick={() => setAddInvestPlan(false)} className="text-amber-600 hover:text-amber-800">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Plan Condition</label>
|
||||
<input type="text" value={newInvestName} onChange={(e) => setNewInvestName(e.target.value)} placeholder="e.g., 1 Bike Plan" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Status</label>
|
||||
<select value={newInvestStatus} onChange={(e) => setNewInvestStatus(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="active">Active</option>
|
||||
<option value="paused">Paused</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Target Amount (৳)</label>
|
||||
<input type="number" value={newInvestTarget} onChange={(e) => setNewInvestTarget(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Start Date</label>
|
||||
<input type="date" value={newInvestStart} onChange={(e) => setNewInvestStart(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">End Date</label>
|
||||
<input type="date" value={newInvestEnd} onChange={(e) => setNewInvestEnd(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Min Investment (৳)</label>
|
||||
<input type="number" value={newInvestMin} onChange={(e) => setNewInvestMin(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Max Investment (৳)</label>
|
||||
<input type="number" value={newInvestMax} onChange={(e) => setNewInvestMax(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Duration (Months)</label>
|
||||
<input type="number" value={newInvestDuration} onChange={(e) => setNewInvestDuration(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Lock-in Period (Months)</label>
|
||||
<input type="number" value={newInvestLock} onChange={(e) => setNewInvestLock(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Early Exit Penalty (%)</label>
|
||||
<input type="number" value={newInvestPenalty} onChange={(e) => setNewInvestPenalty(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Monthly Return (%)</label>
|
||||
<input type="number" step="0.1" value={newInvestMonthly} onChange={(e) => setNewInvestMonthly(parseFloat(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Total Return (%)</label>
|
||||
<input type="number" step="0.1" value={newInvestTotal} onChange={(e) => setNewInvestTotal(parseFloat(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4 mt-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">FICO Share (%) - Single Rent</label>
|
||||
<input type="number" value={newInvestFicoSingleRent} onChange={(e) => setNewInvestFicoSingleRent(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">FICO Share (%) - Rent to Own</label>
|
||||
<input type="number" value={newInvestFicoRentToOwn} onChange={(e) => setNewInvestFicoRentToOwn(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">FICO Share (%) - Share an EV</label>
|
||||
<input type="number" value={newInvestFicoShareEv} onChange={(e) => setNewInvestFicoShareEv(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="text-sm text-slate-600">Description</label>
|
||||
<textarea value={newInvestDesc} onChange={(e) => setNewInvestDesc(e.target.value)} placeholder="Enter plan description" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" rows={2} />
|
||||
</div>
|
||||
<button onClick={createInvestPlan} className="mt-4 px-4 py-2 bg-amber-600 text-white rounded-lg text-sm font-medium">Create Plan</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 border-b border-slate-200">
|
||||
{settings.plans.investment.map((plan, idx) => (
|
||||
<button key={idx} onClick={() => setActiveInvestTab(idx)} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeInvestTab === idx ? 'border-amber-500 text-amber-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}> {plan.name}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{settings.plans.investment.length > 0 && settings.plans.investment.map((plan, idx) => idx === activeInvestTab && (
|
||||
<div key={idx} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="bg-amber-50 px-4 py-3 border-b border-amber-100 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-amber-800">{plan.name} - {plan.monthlyReturnPercent}% per month</h4>
|
||||
<p className="text-sm text-amber-600 mt-1">{plan.description}</p>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${plan.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>{plan.status}</span>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Plan Condition</label>
|
||||
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Status</label>
|
||||
<select value={plan.status} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].status = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="active">Active</option>
|
||||
<option value="paused">Paused</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Target Amount (৳)</label>
|
||||
<input type="number" value={plan.targetAmount} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].targetAmount = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Start Date</label>
|
||||
<input type="date" value={plan.startDate} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].startDate = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">End Date</label>
|
||||
<input type="date" value={plan.endDate} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].endDate = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Min Investment (৳)</label>
|
||||
<input type="number" value={plan.minInvestment} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].minInvestment = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Max Investment (৳)</label>
|
||||
<input type="number" value={plan.maxInvestment} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].maxInvestment = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Duration (Months)</label>
|
||||
<input type="number" value={plan.durationMonths} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].durationMonths = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Lock-in Period (Months)</label>
|
||||
<input type="number" value={plan.lockInMonths} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].lockInMonths = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Early Exit Penalty (%)</label>
|
||||
<input type="number" value={plan.earlyExitPenalty} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].earlyExitPenalty = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Monthly Return (%)</label>
|
||||
<input type="number" step="0.1" value={plan.monthlyReturnPercent} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].monthlyReturnPercent = parseFloat(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Total Return (%)</label>
|
||||
<input type="number" step="0.1" value={plan.totalReturnPercent} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].totalReturnPercent = parseFloat(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4 mt-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">FICO Share (%) - Single Rent</label>
|
||||
<input type="number" value={(plan as any).ficoSingleRent} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].ficoSingleRent = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">FICO Share (%) - Rent to Own</label>
|
||||
<input type="number" value={(plan as any).ficoRentToOwn} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].ficoRentToOwn = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">FICO Share (%) - Share an EV</label>
|
||||
<input type="number" value={(plan as any).ficoShareEv} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].ficoShareEv = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="text-sm text-slate-600">Description</label>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" rows={2} />
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button onClick={handleSave} className="px-4 py-2 bg-amber-600 text-white rounded-lg text-sm font-medium flex items-center gap-2">
|
||||
<Save className="w-4 h-4" /> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
401
src/app/admin/settings/components/KycSettings.tsx
Normal file
401
src/app/admin/settings/components/KycSettings.tsx
Normal file
@@ -0,0 +1,401 @@
|
||||
'use client';
|
||||
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface KycSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
activeMasterTab: 'investor' | 'merchant' | 'swapstation' | 'rentalType';
|
||||
setActiveMasterTab: (tab: 'investor' | 'merchant' | 'swapstation' | 'rentalType') => void;
|
||||
addDocType: 'investor' | 'merchant' | 'swapstation' | 'rental' | null;
|
||||
setAddDocType: (type: 'investor' | 'merchant' | 'swapstation' | 'rental' | null) => void;
|
||||
newDocName: string;
|
||||
setNewDocName: (name: string) => void;
|
||||
newDocDesc: string;
|
||||
setNewDocDesc: (desc: string) => void;
|
||||
}
|
||||
|
||||
export default function KycSettings({
|
||||
settings,
|
||||
setSettings,
|
||||
activeMasterTab,
|
||||
setActiveMasterTab,
|
||||
addDocType,
|
||||
setAddDocType,
|
||||
newDocName,
|
||||
setNewDocName,
|
||||
newDocDesc,
|
||||
setNewDocDesc,
|
||||
}: KycSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">KYC Documents</h3>
|
||||
|
||||
<div className="flex flex-wrap gap-2 border-b border-slate-200 pb-2">
|
||||
{[
|
||||
{ id: 'investor', label: 'Investor' },
|
||||
{ id: 'merchant', label: 'Merchant' },
|
||||
{ id: 'swapstation', label: 'Swap Station' },
|
||||
{ id: 'rentalType', label: 'Rental Types' },
|
||||
].map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveMasterTab(tab.id as typeof activeMasterTab)}
|
||||
className={`px-3 py-1.5 text-sm rounded-lg ${activeMasterTab === tab.id
|
||||
? 'bg-accent text-white'
|
||||
: 'text-slate-600 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{activeMasterTab === 'investor' && (
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-700 mb-3">Investor Documents</h4>
|
||||
<div className="space-y-2">
|
||||
{settings.masterData.investorDocuments.map((doc, i) => (
|
||||
<div key={doc.id} className="flex items-center gap-3 p-3 border border-slate-200 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={doc.required}
|
||||
onChange={(e) => {
|
||||
const updated = [...settings.masterData.investorDocuments];
|
||||
updated[i].required = e.target.checked;
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, investorDocuments: updated } });
|
||||
}}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{doc.name}</p>
|
||||
<p className="text-xs text-slate-500">{doc.description}</p>
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${doc.required ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-500'}`}>
|
||||
{doc.required ? 'Required' : 'Optional'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setAddDocType('investor')}
|
||||
className="mt-4 text-sm text-accent hover:underline"
|
||||
>
|
||||
+ Add Document
|
||||
</button>
|
||||
{addDocType === 'investor' && (
|
||||
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Document name"
|
||||
value={newDocName}
|
||||
onChange={(e) => setNewDocName(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={newDocDesc}
|
||||
onChange={(e) => setNewDocDesc(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (newDocName.trim()) {
|
||||
const updated = [...settings.masterData.investorDocuments, {
|
||||
id: `inv_${Date.now()}`,
|
||||
name: newDocName,
|
||||
required: false,
|
||||
description: newDocDesc || ''
|
||||
}];
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, investorDocuments: updated } });
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
setAddDocType(null);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setAddDocType(null);
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
}}
|
||||
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeMasterTab === 'merchant' && (
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-700 mb-3">Merchant Documents</h4>
|
||||
<div className="space-y-2">
|
||||
{settings.masterData.merchantDocuments.map((doc, i) => (
|
||||
<div key={doc.id} className="flex items-center gap-3 p-3 border border-slate-200 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={doc.required}
|
||||
onChange={(e) => {
|
||||
const updated = [...settings.masterData.merchantDocuments];
|
||||
updated[i].required = e.target.checked;
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, merchantDocuments: updated } });
|
||||
}}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{doc.name}</p>
|
||||
<p className="text-xs text-slate-500">{doc.description}</p>
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${doc.required ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-500'}`}>
|
||||
{doc.required ? 'Required' : 'Optional'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setAddDocType('merchant')}
|
||||
className="mt-4 text-sm text-accent hover:underline"
|
||||
>
|
||||
+ Add Document
|
||||
</button>
|
||||
{addDocType === 'merchant' && (
|
||||
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Document name"
|
||||
value={newDocName}
|
||||
onChange={(e) => setNewDocName(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={newDocDesc}
|
||||
onChange={(e) => setNewDocDesc(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (newDocName.trim()) {
|
||||
const updated = [...settings.masterData.merchantDocuments, {
|
||||
id: `mer_${Date.now()}`,
|
||||
name: newDocName,
|
||||
required: false,
|
||||
description: newDocDesc || ''
|
||||
}];
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, merchantDocuments: updated } });
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
setAddDocType(null);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setAddDocType(null);
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
}}
|
||||
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeMasterTab === 'swapstation' && (
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-700 mb-3">Swap Station Documents</h4>
|
||||
<div className="space-y-2">
|
||||
{settings.masterData.swapStationDocuments.map((doc, i) => (
|
||||
<div key={doc.id} className="flex items-center gap-3 p-3 border border-slate-200 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={doc.required}
|
||||
onChange={(e) => {
|
||||
const updated = [...settings.masterData.swapStationDocuments];
|
||||
updated[i].required = e.target.checked;
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, swapStationDocuments: updated } });
|
||||
}}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{doc.name}</p>
|
||||
<p className="text-xs text-slate-500">{doc.description}</p>
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${doc.required ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-500'}`}>
|
||||
{doc.required ? 'Required' : 'Optional'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setAddDocType('swapstation')}
|
||||
className="mt-4 text-sm text-accent hover:underline"
|
||||
>
|
||||
+ Add Document
|
||||
</button>
|
||||
{addDocType === 'swapstation' && (
|
||||
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Document name"
|
||||
value={newDocName}
|
||||
onChange={(e) => setNewDocName(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={newDocDesc}
|
||||
onChange={(e) => setNewDocDesc(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (newDocName.trim()) {
|
||||
const updated = [...settings.masterData.swapStationDocuments, {
|
||||
id: `ss_${Date.now()}`,
|
||||
name: newDocName,
|
||||
required: false,
|
||||
description: newDocDesc || ''
|
||||
}];
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, swapStationDocuments: updated } });
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
setAddDocType(null);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setAddDocType(null);
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
}}
|
||||
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeMasterTab === 'rentalType' && (
|
||||
<div className="space-y-6">
|
||||
{settings.masterData.rentalDocuments.map((rental, ri) => (
|
||||
<div key={rental.type} className="border border-slate-200 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="font-medium text-slate-700">{rental.name}</h4>
|
||||
<span className="text-xs px-2 py-1 bg-blue-100 text-blue-700 rounded">{rental.documents.length} docs</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{rental.documents.map((doc, di) => (
|
||||
<div key={doc.id} className="flex items-center gap-3 p-2 bg-slate-50 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={doc.required}
|
||||
onChange={(e) => {
|
||||
const updated = [...settings.masterData.rentalDocuments];
|
||||
updated[ri].documents[di].required = e.target.checked;
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, rentalDocuments: updated } });
|
||||
}}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm">{doc.name}</p>
|
||||
<p className="text-xs text-slate-500">{doc.description}</p>
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${doc.required ? 'bg-red-100 text-red-700' : 'bg-slate-200 text-slate-600'}`}>
|
||||
{doc.required ? 'Required' : 'Optional'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setAddDocType('rental')}
|
||||
className="mt-3 text-sm text-accent hover:underline"
|
||||
>
|
||||
+ Add Document
|
||||
</button>
|
||||
{addDocType === 'rental' && (
|
||||
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Document name"
|
||||
value={newDocName}
|
||||
onChange={(e) => setNewDocName(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={newDocDesc}
|
||||
onChange={(e) => setNewDocDesc(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (newDocName.trim()) {
|
||||
const updated = [...settings.masterData.rentalDocuments];
|
||||
updated.forEach((rental) => {
|
||||
rental.documents.push({
|
||||
id: `rent_${Date.now()}`,
|
||||
name: newDocName,
|
||||
required: false,
|
||||
description: newDocDesc || ''
|
||||
});
|
||||
});
|
||||
setSettings({ ...settings, masterData: { ...settings.masterData, rentalDocuments: updated } });
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
setAddDocType(null);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
|
||||
>
|
||||
Add to All
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setAddDocType(null);
|
||||
setNewDocName('');
|
||||
setNewDocDesc('');
|
||||
}}
|
||||
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
93
src/app/admin/settings/components/LandingSettings.tsx
Normal file
93
src/app/admin/settings/components/LandingSettings.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
'use client';
|
||||
|
||||
import { Image } from 'lucide-react';
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface LandingSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
}
|
||||
|
||||
export default function LandingSettings({ settings, setSettings }: LandingSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Landing Page Hero Section</h3>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Hero Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.heroData.title}
|
||||
onChange={(e) => setSettings({ ...settings, heroData: { ...settings.heroData, title: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.heroData.subtitle}
|
||||
onChange={(e) => setSettings({ ...settings, heroData: { ...settings.heroData, subtitle: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">CTA Button Text</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.heroData.ctaText}
|
||||
onChange={(e) => setSettings({ ...settings, heroData: { ...settings.heroData, ctaText: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">CTA Link</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.heroData.ctaLink}
|
||||
onChange={(e) => setSettings({ ...settings, heroData: { ...settings.heroData, ctaLink: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Background Image</label>
|
||||
<div className="mt-2 border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-accent cursor-pointer">
|
||||
<Image className="w-12 h-12 mx-auto text-slate-400" />
|
||||
<p className="text-sm text-slate-500 mt-2">Upload Background Image</p>
|
||||
<p className="text-xs text-slate-400">Recommended: 1920x1080px</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Video URL (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.heroData.videoUrl}
|
||||
onChange={(e) => setSettings({ ...settings, heroData: { ...settings.heroData, videoUrl: e.target.value } })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
|
||||
placeholder="https://youtube.com/watch?v=..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t">
|
||||
<label className="text-sm text-slate-600">Preview</label>
|
||||
<div className="mt-2 p-8 bg-gradient-to-br from-slate-800 to-slate-900 rounded-lg text-center">
|
||||
<h4 className="text-2xl font-bold text-white">{settings.heroData.title}</h4>
|
||||
<p className="text-slate-300 mt-2">{settings.heroData.subtitle}</p>
|
||||
<button
|
||||
className="mt-4 px-6 py-2 bg-accent text-white rounded-lg"
|
||||
style={{ borderRadius: settings.borderRadius }}
|
||||
>
|
||||
{settings.heroData.ctaText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
127
src/app/admin/settings/components/PartsSettings.tsx
Normal file
127
src/app/admin/settings/components/PartsSettings.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
'use client';
|
||||
|
||||
import { Plus, Pencil, Trash2 } from 'lucide-react';
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface PartsSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
showAddPolicy: boolean;
|
||||
setShowAddPolicy: (show: boolean) => void;
|
||||
newPolicyName: string;
|
||||
setNewPolicyName: (name: string) => void;
|
||||
newPolicyDesc: string;
|
||||
setNewPolicyDesc: (desc: string) => void;
|
||||
editPolicyDescHtml: string;
|
||||
setEditPolicyDescHtml: (desc: string) => void;
|
||||
editingPolicy: { tab: string; index: number } | null;
|
||||
setEditingPolicy: (policy: { tab: string; index: number } | null) => void;
|
||||
editPolicyName: string;
|
||||
setEditPolicyName: (name: string) => void;
|
||||
editPolicyDesc: string;
|
||||
setEditPolicyDesc: (desc: string) => void;
|
||||
}
|
||||
|
||||
export default function PartsSettings({
|
||||
settings,
|
||||
setSettings,
|
||||
showAddPolicy,
|
||||
setShowAddPolicy,
|
||||
newPolicyName,
|
||||
setNewPolicyName,
|
||||
newPolicyDesc,
|
||||
setNewPolicyDesc,
|
||||
editPolicyDescHtml,
|
||||
setEditPolicyDescHtml,
|
||||
editingPolicy,
|
||||
setEditingPolicy,
|
||||
editPolicyName,
|
||||
setEditPolicyName,
|
||||
editPolicyDesc,
|
||||
setEditPolicyDesc,
|
||||
}: PartsSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-slate-800">EV Parts</h3>
|
||||
<button onClick={() => setShowAddPolicy(true)} className="text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1">
|
||||
<Plus className="w-3 h-3" /> Add Part
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showAddPolicy && (
|
||||
<div className="p-3 bg-white rounded-lg border border-blue-200">
|
||||
<input type="text" value={newPolicyName} onChange={(e) => setNewPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm mb-2" placeholder="Part Name" />
|
||||
<div className="grid grid-cols-2 gap-2 mb-2">
|
||||
<input type="number" value={newPolicyDesc} onChange={(e) => setNewPolicyDesc(e.target.value)} className="px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Buying Price" />
|
||||
<input type="number" value={editPolicyDescHtml} onChange={(e) => setEditPolicyDescHtml(e.target.value)} className="px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Selling Price" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => {
|
||||
if (!newPolicyName.trim()) return;
|
||||
const newId = 'PRT-' + String(settings.parts.length + 1).padStart(3, '0');
|
||||
setSettings({ ...settings, parts: [...settings.parts, { id: newId, name: newPolicyName, buyingPrice: Number(newPolicyDesc), sellingPrice: Number(editPolicyDescHtml) }] });
|
||||
setNewPolicyName('');
|
||||
setNewPolicyDesc('');
|
||||
setEditPolicyDescHtml('');
|
||||
setShowAddPolicy(false);
|
||||
}} className="px-3 py-1.5 bg-blue-600 text-white rounded text-sm">Add</button>
|
||||
<button onClick={() => { setNewPolicyName(''); setNewPolicyDesc(''); setEditPolicyDescHtml(''); setShowAddPolicy(false); }} className="px-3 py-1.5 bg-slate-200 text-slate-600 rounded text-sm">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
{settings.parts.map((part, i) => (
|
||||
<div key={part.id} className="p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
{editingPolicy?.tab === 'parts' && editingPolicy?.index === i ? (
|
||||
<div className="space-y-2">
|
||||
<input type="text" value={editPolicyName} onChange={(e) => setEditPolicyName(e.target.value)} className="w-full px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Part Name" />
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<input type="number" value={editPolicyDesc} onChange={(e) => setEditPolicyDesc(e.target.value)} className="px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Buying Price" />
|
||||
<input type="number" value={newPolicyName} onChange={(e) => setNewPolicyName(e.target.value)} className="px-2 py-1.5 border border-slate-200 rounded text-sm" placeholder="Selling Price" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => {
|
||||
const updated = [...settings.parts];
|
||||
updated[i] = { ...part, name: editPolicyName, buyingPrice: Number(editPolicyDesc), sellingPrice: Number(newPolicyName) };
|
||||
setSettings({ ...settings, parts: updated });
|
||||
setEditingPolicy(null);
|
||||
setEditPolicyName('');
|
||||
setEditPolicyDesc('');
|
||||
setNewPolicyName('');
|
||||
}} className="px-2 py-1 bg-blue-600 text-white rounded text-xs">Save</button>
|
||||
<button onClick={() => { setEditingPolicy(null); setEditPolicyName(''); setEditPolicyDesc(''); setNewPolicyName(''); }} className="px-2 py-1 bg-slate-200 text-slate-600 rounded text-xs">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-700">{part.name}</span>
|
||||
<span className="text-xs text-slate-400">({part.id})</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mt-1">
|
||||
<span className="text-xs text-slate-500">Buying: <span className="text-green-600">৳{part.buyingPrice}</span></span>
|
||||
<span className="text-xs text-slate-500">Selling: <span className="text-amber-600">৳{part.sellingPrice}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
<button onClick={() => { setEditingPolicy({ tab: 'parts', index: i }); setEditPolicyName(part.name); setEditPolicyDesc(String(part.buyingPrice)); setNewPolicyName(String(part.sellingPrice)); }} className="p-1 text-slate-400 hover:text-blue-600">
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button onClick={() => {
|
||||
const updated = settings.parts.filter((_, idx) => idx !== i);
|
||||
setSettings({ ...settings, parts: updated });
|
||||
}} className="p-1 text-slate-400 hover:text-red-600">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
493
src/app/admin/settings/components/PlanSelection.tsx
Normal file
493
src/app/admin/settings/components/PlanSelection.tsx
Normal file
@@ -0,0 +1,493 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Plus, Save, Trash2, X } from 'lucide-react';
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface PlanSelectionProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
activePlanTab: 'singleRent' | 'rentToOwn' | 'shareEv';
|
||||
setActivePlanTab: (tab: 'singleRent' | 'rentToOwn' | 'shareEv') => void;
|
||||
handleSave: () => void;
|
||||
addNewPlan: (type: 'singleRent' | 'rentToOwn' | 'shareEv') => void;
|
||||
}
|
||||
|
||||
export default function PlanSelection({
|
||||
settings,
|
||||
setSettings,
|
||||
activePlanTab,
|
||||
setActivePlanTab,
|
||||
handleSave,
|
||||
addNewPlan,
|
||||
}: PlanSelectionProps) {
|
||||
const [deleteModal, setDeleteModal] = useState<{ type: 'singleRent' | 'rentToOwn' | 'shareEv' | null; idx: number | null }>({ type: null, idx: null });
|
||||
|
||||
const handleDeletePlan = () => {
|
||||
if (deleteModal.type !== null && deleteModal.idx !== null) {
|
||||
const updated = settings.plans[deleteModal.type].filter((_, i) => i !== deleteModal.idx);
|
||||
setSettings({ ...settings, plans: { ...settings.plans, [deleteModal.type]: updated } });
|
||||
setDeleteModal({ type: null, idx: null });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Plan Selection</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 border-b border-slate-200">
|
||||
<button onClick={() => setActivePlanTab('singleRent')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activePlanTab === 'singleRent' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Single Rent</button>
|
||||
<button onClick={() => setActivePlanTab('rentToOwn')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activePlanTab === 'rentToOwn' ? 'border-purple-500 text-purple-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Rent to Own</button>
|
||||
<button onClick={() => setActivePlanTab('shareEv')} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activePlanTab === 'shareEv' ? 'border-green-500 text-green-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>Share an EV</button>
|
||||
</div>
|
||||
|
||||
{activePlanTab === 'singleRent' && (
|
||||
<div className="space-y-6">
|
||||
{settings.plans.singleRent.map((plan, idx) => (
|
||||
<div key={plan.id} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="bg-blue-50 px-4 py-3 border-b border-blue-100 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-800">{plan.name} - ৳{plan.dailyRent}/day</h4>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full bg-transparent text-sm text-blue-600 mt-1 border-0 resize-none p-0 focus:ring-0" rows={1} placeholder="Plan description..." />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-3">
|
||||
<button onClick={handleSave} className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-xs font-medium flex items-center gap-1 hover:bg-blue-700">
|
||||
<Save className="w-3 h-3" /> Save
|
||||
</button>
|
||||
<button onClick={() => setDeleteModal({ type: 'singleRent', idx })} className="px-2 py-1.5 bg-red-50 text-red-600 rounded-lg text-xs font-medium flex items-center gap-1 hover:bg-red-100">
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 space-y-5">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Condition</label>
|
||||
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Condition" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">EV Model Numbers</label>
|
||||
<div className="relative">
|
||||
<button type="button" onClick={() => { const el = document.getElementById(`ev-models-${idx}`); if (el) el.classList.toggle('hidden'); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white text-left flex items-center justify-between cursor-pointer hover:border-slate-300">
|
||||
<span className={plan.evModels.length > 0 ? 'text-slate-800' : 'text-slate-400'}>
|
||||
{plan.evModels.length > 0 ? `${plan.evModels.length} selected` : 'Select EV Models...'}
|
||||
</span>
|
||||
<svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
|
||||
</button>
|
||||
<div id={`ev-models-${idx}`} className="hidden absolute z-50 w-full mt-1 bg-white border border-slate-200 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
||||
{['Etron ET50', 'Yadea DT3', 'AIMA Lightning', 'AIMA EM5', 'Yadea G5', 'TVS iQube', 'Bajaj Chetak', 'Hero Photon', 'Okinawa Praise', 'Ampere Magnus', 'Benling Aura', 'Lectrix LXS', 'Revolt RV400'].map(model => (
|
||||
<label key={model} className="flex items-center gap-2 px-3 py-2 hover:bg-slate-50 cursor-pointer text-sm">
|
||||
<input type="checkbox" checked={plan.evModels.includes(model)} onChange={() => { const updated = [...settings.plans.singleRent]; updated[idx].evModels = updated[idx].evModels.includes(model) ? updated[idx].evModels.filter(m => m !== model) : [...updated[idx].evModels, model]; setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="rounded border-slate-300 text-emerald-600" />
|
||||
<span>{model}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Deposit (৳)</label>
|
||||
<input type="number" value={plan.deposit} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].deposit = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Daily Rent</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.dailyRent} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].dailyRent = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty1} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].dailyRentPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty2} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].dailyRentPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty3} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].dailyRentPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Weekly Subscription</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.weeklySubscription} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].weeklySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty1} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].weeklyPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty2} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].weeklyPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty3} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].weeklyPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Monthly Subscription</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.monthlySubscription} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].monthlySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty1} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].monthlyPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty2} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].monthlyPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty3} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].monthlyPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide block mb-2">Contract Duration (Months)</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{plan.contractMonths.map(month => (
|
||||
<button key={month} onClick={() => { const updated = [...settings.plans.singleRent]; updated[idx].contractMonths = updated[idx].contractMonths.filter(m => m !== month); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="px-3 py-1.5 rounded-lg text-xs font-medium bg-blue-600 text-white hover:bg-red-500 transition-all flex items-center gap-1">
|
||||
{month} {month === 1 ? 'Month' : 'Months'}
|
||||
<span className="ml-1 font-bold">×</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="flex items-center gap-1">
|
||||
<input type="number" min="1" placeholder="Add" className="w-20 px-2 py-1.5 border border-slate-200 rounded-lg text-xs" onKeyDown={(e) => { if (e.key === 'Enter') { const val = parseInt((e.target as HTMLInputElement).value); if (val > 0 && !plan.contractMonths.includes(val)) { const updated = [...settings.plans.singleRent]; updated[idx].contractMonths = [...updated[idx].contractMonths, val].sort((a, b) => a - b); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); (e.target as HTMLInputElement).value = ''; } } }} />
|
||||
<button onClick={(e) => { const input = (e.currentTarget.previousElementSibling as HTMLInputElement); const val = parseInt(input.value); if (val > 0 && !plan.contractMonths.includes(val)) { const updated = [...settings.plans.singleRent]; updated[idx].contractMonths = [...updated[idx].contractMonths, val].sort((a, b) => a - b); setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); input.value = ''; } }} className="px-2 py-1.5 bg-blue-600 text-white rounded-lg text-xs hover:bg-blue-700">+</button>
|
||||
</div>
|
||||
</div>
|
||||
{plan.contractMonths.length === 0 && <p className="text-xs text-slate-400 mt-2">No contract months selected.</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Description</label>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" rows={2} placeholder="Enter plan description..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => addNewPlan('singleRent')} className="w-full py-3 border-2 border-dashed border-slate-300 rounded-xl text-slate-500 hover:border-blue-400 hover:text-blue-500 hover:bg-blue-50 transition-all flex items-center justify-center gap-2 text-sm font-medium">
|
||||
<Plus className="w-4 h-4" /> Add New Plan
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activePlanTab === 'rentToOwn' && (
|
||||
<div className="space-y-6">
|
||||
{settings.plans.rentToOwn.map((plan, idx) => (
|
||||
<div key={plan.id} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="bg-purple-50 px-4 py-3 border-b border-purple-100 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-purple-800">{plan.name} - ৳{plan.dailyRent}/day</h4>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full bg-transparent text-sm text-purple-600 mt-1 border-0 resize-none p-0 focus:ring-0" rows={1} placeholder="Plan description..." />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-3">
|
||||
<button onClick={handleSave} className="px-3 py-1.5 bg-purple-600 text-white rounded-lg text-xs font-medium flex items-center gap-1 hover:bg-purple-700">
|
||||
<Save className="w-3 h-3" /> Save
|
||||
</button>
|
||||
<button onClick={() => setDeleteModal({ type: 'rentToOwn', idx })} className="px-2 py-1.5 bg-red-50 text-red-600 rounded-lg text-xs font-medium flex items-center gap-1 hover:bg-red-100">
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 space-y-5">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Condition</label>
|
||||
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Condition" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">EV Model Numbers</label>
|
||||
<div className="relative">
|
||||
<button type="button" onClick={() => { const el = document.getElementById(`ev-models-rto-${idx}`); if (el) el.classList.toggle('hidden'); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white text-left flex items-center justify-between cursor-pointer hover:border-slate-300">
|
||||
<span className={plan.evModels.length > 0 ? 'text-slate-800' : 'text-slate-400'}>
|
||||
{plan.evModels.length > 0 ? `${plan.evModels.length} selected` : 'Select EV Models...'}
|
||||
</span>
|
||||
<svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
|
||||
</button>
|
||||
<div id={`ev-models-rto-${idx}`} className="hidden absolute z-50 w-full mt-1 bg-white border border-slate-200 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
||||
{['Etron ET50', 'Yadea DT3', 'AIMA Lightning', 'AIMA EM5', 'Yadea G5', 'TVS iQube', 'Bajaj Chetak', 'Hero Photon', 'Okinawa Praise', 'Ampere Magnus', 'Benling Aura', 'Lectrix LXS', 'Revolt RV400'].map(model => (
|
||||
<label key={model} className="flex items-center gap-2 px-3 py-2 hover:bg-slate-50 cursor-pointer text-sm">
|
||||
<input type="checkbox" checked={plan.evModels.includes(model)} onChange={() => { const updated = [...settings.plans.rentToOwn]; updated[idx].evModels = updated[idx].evModels.includes(model) ? updated[idx].evModels.filter(m => m !== model) : [...updated[idx].evModels, model]; setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="rounded border-slate-300 text-emerald-600" />
|
||||
<span>{model}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Deposit (৳)</label>
|
||||
<input type="number" value={plan.deposit} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].deposit = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Daily Rent</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.dailyRent} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].dailyRent = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty1} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].dailyRentPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty2} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].dailyRentPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty3} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].dailyRentPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Weekly Subscription</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.weeklySubscription} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].weeklySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty1} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].weeklyPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty2} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].weeklyPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty3} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].weeklyPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Monthly Subscription</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.monthlySubscription} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].monthlySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty1} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].monthlyPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty2} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].monthlyPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty3} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].monthlyPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide block mb-2">Contract Duration (Months)</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{plan.contractMonths.map(month => (
|
||||
<button key={month} onClick={() => { const updated = [...settings.plans.rentToOwn]; updated[idx].contractMonths = updated[idx].contractMonths.filter(m => m !== month); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="px-3 py-1.5 rounded-lg text-xs font-medium bg-purple-600 text-white hover:bg-red-500 transition-all flex items-center gap-1">
|
||||
{month} {month === 1 ? 'Month' : 'Months'}
|
||||
<span className="ml-1 font-bold">×</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="flex items-center gap-1">
|
||||
<input type="number" min="1" placeholder="Add" className="w-20 px-2 py-1.5 border border-slate-200 rounded-lg text-xs" onKeyDown={(e) => { if (e.key === 'Enter') { const val = parseInt((e.target as HTMLInputElement).value); if (val > 0 && !plan.contractMonths.includes(val)) { const updated = [...settings.plans.rentToOwn]; updated[idx].contractMonths = [...updated[idx].contractMonths, val].sort((a, b) => a - b); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); (e.target as HTMLInputElement).value = ''; } } }} />
|
||||
<button onClick={(e) => { const input = (e.currentTarget.previousElementSibling as HTMLInputElement); const val = parseInt(input.value); if (val > 0 && !plan.contractMonths.includes(val)) { const updated = [...settings.plans.rentToOwn]; updated[idx].contractMonths = [...updated[idx].contractMonths, val].sort((a, b) => a - b); setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); input.value = ''; } }} className="px-2 py-1.5 bg-purple-600 text-white rounded-lg text-xs hover:bg-purple-700">+</button>
|
||||
</div>
|
||||
</div>
|
||||
{plan.contractMonths.length === 0 && <p className="text-xs text-slate-400 mt-2">No contract months selected.</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Description</label>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" rows={2} placeholder="Enter plan description..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => addNewPlan('rentToOwn')} className="w-full py-3 border-2 border-dashed border-slate-300 rounded-xl text-slate-500 hover:border-purple-400 hover:text-purple-500 hover:bg-purple-50 transition-all flex items-center justify-center gap-2 text-sm font-medium">
|
||||
<Plus className="w-4 h-4" /> Add New Plan
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activePlanTab === 'shareEv' && (
|
||||
<div className="space-y-6">
|
||||
{settings.plans.shareEv.map((plan, idx) => (
|
||||
<div key={plan.id} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="bg-green-50 px-4 py-3 border-b border-green-100 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-green-800">{plan.name} - ৳{plan.dailyRentEach}/day each</h4>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full bg-transparent text-sm text-green-600 mt-1 border-0 resize-none p-0 focus:ring-0" rows={1} placeholder="Plan description..." />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-3">
|
||||
<button onClick={handleSave} className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-xs font-medium flex items-center gap-1 hover:bg-green-700">
|
||||
<Save className="w-3 h-3" /> Save
|
||||
</button>
|
||||
<button onClick={() => setDeleteModal({ type: 'shareEv', idx })} className="px-2 py-1.5 bg-red-50 text-red-600 rounded-lg text-xs font-medium flex items-center gap-1 hover:bg-red-100">
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-5 space-y-5">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Condition</label>
|
||||
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Condition" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">EV Model Numbers</label>
|
||||
<div className="relative">
|
||||
<button type="button" onClick={() => { const el = document.getElementById(`ev-models-se-${idx}`); if (el) el.classList.toggle('hidden'); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white text-left flex items-center justify-between cursor-pointer hover:border-slate-300">
|
||||
<span className={plan.evModels.length > 0 ? 'text-slate-800' : 'text-slate-400'}>
|
||||
{plan.evModels.length > 0 ? `${plan.evModels.length} selected` : 'Select EV Models...'}
|
||||
</span>
|
||||
<svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
|
||||
</button>
|
||||
<div id={`ev-models-se-${idx}`} className="hidden absolute z-50 w-full mt-1 bg-white border border-slate-200 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
||||
{['Etron ET50', 'Yadea DT3', 'AIMA Lightning', 'AIMA EM5', 'Yadea G5', 'TVS iQube', 'Bajaj Chetak', 'Hero Photon', 'Okinawa Praise', 'Ampere Magnus', 'Benling Aura', 'Lectrix LXS', 'Revolt RV400'].map(model => (
|
||||
<label key={model} className="flex items-center gap-2 px-3 py-2 hover:bg-slate-50 cursor-pointer text-sm">
|
||||
<input type="checkbox" checked={plan.evModels.includes(model)} onChange={() => { const updated = [...settings.plans.shareEv]; updated[idx].evModels = updated[idx].evModels.includes(model) ? updated[idx].evModels.filter(m => m !== model) : [...updated[idx].evModels, model]; setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="rounded border-slate-300 text-emerald-600" />
|
||||
<span>{model}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Deposit Each (৳)</label>
|
||||
<input type="number" value={plan.depositEach} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].depositEach = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Daily Rent (Each)</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.dailyRentEach} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].dailyRentEach = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty1} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].dailyRentPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty2} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].dailyRentPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.dailyRentPenalty3} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].dailyRentPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Weekly Subscription (Each)</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.weeklySubscriptionEach} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].weeklySubscriptionEach = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty1} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].weeklyPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty2} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].weeklyPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.weeklyPenalty3} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].weeklyPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Monthly Subscription (Each)</label>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||
<input type="number" value={plan.monthlySubscriptionEach} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].monthlySubscriptionEach = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">1st Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty1} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].monthlyPenalty1 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">2nd Day Penalty (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty2} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].monthlyPenalty2 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500">3rd Day Penalty + Bike Lock (৳)</label>
|
||||
<input type="number" value={plan.monthlyPenalty3} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].monthlyPenalty3 = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-2 py-1.5 border border-slate-200 rounded-lg text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-3">
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide block mb-2">Contract Duration (Months)</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{plan.contractMonths.map(month => (
|
||||
<button key={month} onClick={() => { const updated = [...settings.plans.shareEv]; updated[idx].contractMonths = updated[idx].contractMonths.filter(m => m !== month); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="px-3 py-1.5 rounded-lg text-xs font-medium bg-green-600 text-white hover:bg-red-500 transition-all flex items-center gap-1">
|
||||
{month} {month === 1 ? 'Month' : 'Months'}
|
||||
<span className="ml-1 font-bold">×</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="flex items-center gap-1">
|
||||
<input type="number" min="1" placeholder="Add" className="w-20 px-2 py-1.5 border border-slate-200 rounded-lg text-xs" onKeyDown={(e) => { if (e.key === 'Enter') { const val = parseInt((e.target as HTMLInputElement).value); if (val > 0 && !plan.contractMonths.includes(val)) { const updated = [...settings.plans.shareEv]; updated[idx].contractMonths = [...updated[idx].contractMonths, val].sort((a, b) => a - b); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); (e.target as HTMLInputElement).value = ''; } } }} />
|
||||
<button onClick={(e) => { const input = (e.currentTarget.previousElementSibling as HTMLInputElement); const val = parseInt(input.value); if (val > 0 && !plan.contractMonths.includes(val)) { const updated = [...settings.plans.shareEv]; updated[idx].contractMonths = [...updated[idx].contractMonths, val].sort((a, b) => a - b); setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); input.value = ''; } }} className="px-2 py-1.5 bg-green-600 text-white rounded-lg text-xs hover:bg-green-700">+</button>
|
||||
</div>
|
||||
</div>
|
||||
{plan.contractMonths.length === 0 && <p className="text-xs text-slate-400 mt-2">No contract months selected.</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Description</label>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" rows={2} placeholder="Enter plan description..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => addNewPlan('shareEv')} className="w-full py-3 border-2 border-dashed border-slate-300 rounded-xl text-slate-500 hover:border-green-400 hover:text-green-500 hover:bg-green-50 transition-all flex items-center justify-center gap-2 text-sm font-medium">
|
||||
<Plus className="w-4 h-4" /> Add New Plan
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{deleteModal.type !== null && deleteModal.idx !== null && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-sm">
|
||||
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
|
||||
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||
<Trash2 className="w-5 h-5 text-red-500" /> Delete Plan
|
||||
</h3>
|
||||
<button onClick={() => setDeleteModal({ type: null, idx: null })} className="text-slate-400 hover:text-slate-600 text-2xl">×</button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<p className="text-sm text-slate-600">Are you sure you want to delete this plan? This action cannot be undone.</p>
|
||||
{deleteModal.type && deleteModal.idx !== null && settings.plans[deleteModal.type][deleteModal.idx] && (
|
||||
<div className="mt-3 p-3 bg-slate-50 rounded-lg">
|
||||
<p className="text-sm font-medium text-slate-700">{settings.plans[deleteModal.type][deleteModal.idx].name}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
||||
<button onClick={() => setDeleteModal({ type: null, idx: null })} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm">Cancel</button>
|
||||
<button onClick={handleDeletePlan} className="px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700">Delete Plan</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
234
src/app/admin/settings/components/RiderRequestSettings.tsx
Normal file
234
src/app/admin/settings/components/RiderRequestSettings.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
'use client';
|
||||
|
||||
import { Plus, X, Save } from 'lucide-react';
|
||||
import type { CompanySettings } from '../page';
|
||||
|
||||
interface RiderRequestSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
activeRiderTab: number;
|
||||
setActiveRiderTab: (n: number) => void;
|
||||
addRiderPlan: boolean;
|
||||
setAddRiderPlan: (v: boolean) => void;
|
||||
newRiderName: string;
|
||||
setNewRiderName: (v: string) => void;
|
||||
newRiderTier: string;
|
||||
setNewRiderTier: (v: string) => void;
|
||||
newRiderStatus: string;
|
||||
setNewRiderStatus: (v: string) => void;
|
||||
newRiderMin: number;
|
||||
setNewRiderMin: (n: number) => void;
|
||||
newRiderMax: number;
|
||||
setNewRiderMax: (n: number) => void;
|
||||
newRiderMonthly: number;
|
||||
setNewRiderMonthly: (n: number) => void;
|
||||
newRiderDaily: number;
|
||||
setNewRiderDaily: (n: number) => void;
|
||||
newRiderDeposit: number;
|
||||
setNewRiderDeposit: (n: number) => void;
|
||||
newRiderCommission: number;
|
||||
setNewRiderCommission: (n: number) => void;
|
||||
newRiderTarget: number;
|
||||
setNewRiderTarget: (n: number) => void;
|
||||
newRiderHoliday: number;
|
||||
setNewRiderHoliday: (n: number) => void;
|
||||
newRiderDesc: string;
|
||||
setNewRiderDesc: (v: string) => void;
|
||||
createRiderPlan: () => void;
|
||||
handleSave: () => void;
|
||||
}
|
||||
|
||||
export default function RiderRequestSettings({
|
||||
settings, setSettings,
|
||||
activeRiderTab, setActiveRiderTab,
|
||||
addRiderPlan, setAddRiderPlan,
|
||||
newRiderName, setNewRiderName,
|
||||
newRiderTier, setNewRiderTier,
|
||||
newRiderStatus, setNewRiderStatus,
|
||||
newRiderMin, setNewRiderMin,
|
||||
newRiderMax, setNewRiderMax,
|
||||
newRiderMonthly, setNewRiderMonthly,
|
||||
newRiderDaily, setNewRiderDaily,
|
||||
newRiderDeposit, setNewRiderDeposit,
|
||||
newRiderCommission, setNewRiderCommission,
|
||||
newRiderTarget, setNewRiderTarget,
|
||||
newRiderHoliday, setNewRiderHoliday,
|
||||
newRiderDesc, setNewRiderDesc,
|
||||
createRiderPlan, handleSave,
|
||||
}: RiderRequestSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Rider Request Plans</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between bg-rose-50 border border-rose-200 rounded-xl p-4">
|
||||
<div>
|
||||
<h4 className="font-semibold text-rose-800">Rider Request Plans ({settings.plans.riderRequest.length})</h4>
|
||||
<p className="text-sm text-rose-600">Manage rider request plans for operators</p>
|
||||
</div>
|
||||
<button onClick={() => { setAddRiderPlan(true); setNewRiderName(''); }} className="px-4 py-2 bg-rose-600 text-white rounded-lg text-sm font-medium flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> New Plan
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{addRiderPlan && (
|
||||
<div className="bg-white rounded-xl border border-rose-300 overflow-hidden">
|
||||
<div className="bg-rose-100 px-4 py-3 border-b border-rose-200 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-rose-800">New Rider Request Plan</h4>
|
||||
<p className="text-sm text-rose-600 mt-1">Fill in the details below</p>
|
||||
</div>
|
||||
<button onClick={() => setAddRiderPlan(false)} className="text-rose-600 hover:text-rose-800">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Plan Condition</label>
|
||||
<input type="text" value={newRiderName} onChange={(e) => setNewRiderName(e.target.value)} placeholder="e.g., Premium Rider Plan" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Tier</label>
|
||||
<select value={newRiderTier} onChange={(e) => setNewRiderTier(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="Premium">Premium</option>
|
||||
<option value="Standard">Standard</option>
|
||||
<option value="Economy">Economy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Status</label>
|
||||
<select value={newRiderStatus} onChange={(e) => setNewRiderStatus(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="active">Active</option>
|
||||
<option value="paused">Paused</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Min Riders</label>
|
||||
<input type="number" value={newRiderMin} onChange={(e) => setNewRiderMin(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Max Riders</label>
|
||||
<input type="number" value={newRiderMax} onChange={(e) => setNewRiderMax(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Monthly Subscription (৳)</label>
|
||||
<input type="number" value={newRiderMonthly} onChange={(e) => setNewRiderMonthly(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Daily Subscription (৳)</label>
|
||||
<input type="number" value={newRiderDaily} onChange={(e) => setNewRiderDaily(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Deposit (৳)</label>
|
||||
<input type="number" value={newRiderDeposit} onChange={(e) => setNewRiderDeposit(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Commission (%)</label>
|
||||
<input type="number" value={newRiderCommission} onChange={(e) => setNewRiderCommission(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Daily Ride Target</label>
|
||||
<input type="number" value={newRiderTarget} onChange={(e) => setNewRiderTarget(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Weekly Holiday</label>
|
||||
<input type="number" value={newRiderHoliday} onChange={(e) => setNewRiderHoliday(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="text-sm text-slate-600">Description</label>
|
||||
<textarea value={newRiderDesc} onChange={(e) => setNewRiderDesc(e.target.value)} placeholder="Enter plan description" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" rows={2} />
|
||||
</div>
|
||||
<button onClick={createRiderPlan} className="mt-4 px-4 py-2 bg-rose-600 text-white rounded-lg text-sm font-medium">Create Plan</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 border-b border-slate-200">
|
||||
{settings.plans.riderRequest.map((plan, idx) => (
|
||||
<button key={idx} onClick={() => setActiveRiderTab(idx)} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeRiderTab === idx ? 'border-rose-500 text-rose-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}>{idx + 1}. {plan.name}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{settings.plans.riderRequest.length > 0 && settings.plans.riderRequest.map((plan, idx) => idx === activeRiderTab && (
|
||||
<div key={idx} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="bg-rose-50 px-4 py-3 border-b border-rose-100 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-rose-800">{plan.name} - {plan.tier}</h4>
|
||||
<p className="text-sm text-rose-600 mt-1">{plan.description}</p>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${plan.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>{plan.status}</span>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Plan Condition</label>
|
||||
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Tier</label>
|
||||
<select value={plan.tier} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].tier = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="Premium">Premium</option>
|
||||
<option value="Standard">Standard</option>
|
||||
<option value="Economy">Economy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Status</label>
|
||||
<select value={plan.status} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].status = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="active">Active</option>
|
||||
<option value="paused">Paused</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Min Riders</label>
|
||||
<input type="number" value={plan.minRiders} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].minRiders = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Max Riders</label>
|
||||
<input type="number" value={plan.maxRiders} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].maxRiders = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Monthly Subscription (৳)</label>
|
||||
<input type="number" value={plan.monthlySubscription} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].monthlySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Daily Subscription (৳)</label>
|
||||
<input type="number" value={plan.dailySubscription} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].dailySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Deposit (৳)</label>
|
||||
<input type="number" value={plan.deposit} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].deposit = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Commission (%)</label>
|
||||
<input type="number" value={plan.commissionPercent} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].commissionPercent = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Daily Ride Target</label>
|
||||
<input type="number" value={plan.dailyRideTarget} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].dailyRideTarget = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Weekly Holiday</label>
|
||||
<input type="number" value={plan.weeklyHoliday} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].weeklyHoliday = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="text-sm text-slate-600">Description</label>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" rows={2} />
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button onClick={handleSave} className="px-4 py-2 bg-rose-600 text-white rounded-lg text-sm font-medium flex items-center gap-2">
|
||||
<Save className="w-4 h-4" /> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
74
src/app/admin/settings/components/SocialSettings.tsx
Normal file
74
src/app/admin/settings/components/SocialSettings.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
'use client';
|
||||
|
||||
import { CompanySettings } from '../page';
|
||||
|
||||
interface SocialSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
}
|
||||
|
||||
export default function SocialSettings({ settings, setSettings }: SocialSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Social Media Links</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-5 text-blue-600 font-bold text-sm">FB</span>
|
||||
<label className="w-24 text-sm text-slate-600">Facebook</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.socialLinks.facebook}
|
||||
onChange={(e) => setSettings({ ...settings, socialLinks: { ...settings.socialLinks, facebook: e.target.value } })}
|
||||
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="https://facebook.com/yourpage"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-5 text-sky-500 font-bold text-sm">X</span>
|
||||
<label className="w-24 text-sm text-slate-600">Twitter</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.socialLinks.twitter}
|
||||
onChange={(e) => setSettings({ ...settings, socialLinks: { ...settings.socialLinks, twitter: e.target.value } })}
|
||||
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="https://twitter.com/yourpage"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-5 text-pink-600 font-bold text-sm">IG</span>
|
||||
<label className="w-24 text-sm text-slate-600">Instagram</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.socialLinks.instagram}
|
||||
onChange={(e) => setSettings({ ...settings, socialLinks: { ...settings.socialLinks, instagram: e.target.value } })}
|
||||
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="https://instagram.com/yourpage"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-5 text-blue-700 font-bold text-sm">IN</span>
|
||||
<label className="w-24 text-sm text-slate-600">LinkedIn</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.socialLinks.linkedin}
|
||||
onChange={(e) => setSettings({ ...settings, socialLinks: { ...settings.socialLinks, linkedin: e.target.value } })}
|
||||
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="https://linkedin.com/company/yourpage"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-5 text-red-600 font-bold text-sm">YT</span>
|
||||
<label className="w-24 text-sm text-slate-600">YouTube</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.socialLinks.youtube}
|
||||
onChange={(e) => setSettings({ ...settings, socialLinks: { ...settings.socialLinks, youtube: e.target.value } })}
|
||||
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
placeholder="https://youtube.com/@yourchannel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
204
src/app/admin/settings/components/SwapStationSettings.tsx
Normal file
204
src/app/admin/settings/components/SwapStationSettings.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
'use client';
|
||||
|
||||
import { Plus, X, Save } from 'lucide-react';
|
||||
import type { CompanySettings } from '../page';
|
||||
|
||||
interface SwapStationSettingsProps {
|
||||
settings: CompanySettings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||
activeSwapTab: number;
|
||||
setActiveSwapTab: (n: number) => void;
|
||||
addSwapStationPlan: boolean;
|
||||
setAddSwapStationPlan: (v: boolean) => void;
|
||||
newSwapName: string;
|
||||
setNewSwapName: (v: string) => void;
|
||||
newSwapStatus: string;
|
||||
setNewSwapStatus: (v: string) => void;
|
||||
newSwapBatteryCount: number;
|
||||
setNewSwapBatteryCount: (n: number) => void;
|
||||
newSwapPrice: number;
|
||||
setNewSwapPrice: (n: number) => void;
|
||||
newSwapMonthly: number;
|
||||
setNewSwapMonthly: (n: number) => void;
|
||||
newSwapDaily: number;
|
||||
setNewSwapDaily: (n: number) => void;
|
||||
newSwapMin: number;
|
||||
setNewSwapMin: (n: number) => void;
|
||||
newSwapMax: number;
|
||||
setNewSwapMax: (n: number) => void;
|
||||
newSwapProfit: number;
|
||||
setNewSwapProfit: (n: number) => void;
|
||||
newSwapDesc: string;
|
||||
setNewSwapDesc: (v: string) => void;
|
||||
createSwapStationPlan: () => void;
|
||||
handleSave: () => void;
|
||||
}
|
||||
|
||||
export default function SwapStationSettings({
|
||||
settings, setSettings,
|
||||
activeSwapTab, setActiveSwapTab,
|
||||
addSwapStationPlan, setAddSwapStationPlan,
|
||||
newSwapName, setNewSwapName,
|
||||
newSwapStatus, setNewSwapStatus,
|
||||
newSwapBatteryCount, setNewSwapBatteryCount,
|
||||
newSwapPrice, setNewSwapPrice,
|
||||
newSwapMonthly, setNewSwapMonthly,
|
||||
newSwapDaily, setNewSwapDaily,
|
||||
newSwapMin, setNewSwapMin,
|
||||
newSwapMax, setNewSwapMax,
|
||||
newSwapProfit, setNewSwapProfit,
|
||||
newSwapDesc, setNewSwapDesc,
|
||||
createSwapStationPlan, handleSave,
|
||||
}: SwapStationSettingsProps) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Swap Station Plans</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between bg-blue-50 border border-blue-200 rounded-xl p-4">
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-800">Swap Station Plans ({settings.plans.swapStation.length})</h4>
|
||||
<p className="text-sm text-blue-600">Manage swap station plans for operators</p>
|
||||
</div>
|
||||
<button onClick={() => { setAddSwapStationPlan(true); setNewSwapName(''); }} className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium flex items-center gap-2">
|
||||
<Plus className="w-4 h-4" /> New Plan
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{addSwapStationPlan && (
|
||||
<div className="bg-white rounded-xl border border-blue-300 overflow-hidden">
|
||||
<div className="bg-blue-100 px-4 py-3 border-b border-blue-200 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-800">New Swap Station Plan</h4>
|
||||
<p className="text-sm text-blue-600 mt-1">Fill in the details below</p>
|
||||
</div>
|
||||
<button onClick={() => setAddSwapStationPlan(false)} className="text-blue-600 hover:text-blue-800">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Plan Condition</label>
|
||||
<input type="text" value={newSwapName} onChange={(e) => setNewSwapName(e.target.value)} placeholder="e.g., Standard Station" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Status</label>
|
||||
<select value={newSwapStatus} onChange={(e) => setNewSwapStatus(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="active">Active</option>
|
||||
<option value="paused">Paused</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Battery Count</label>
|
||||
<input type="number" value={newSwapBatteryCount} onChange={(e) => setNewSwapBatteryCount(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Swap Price (৳)</label>
|
||||
<input type="number" value={newSwapPrice} onChange={(e) => setNewSwapPrice(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Monthly Subscription (৳)</label>
|
||||
<input type="number" value={newSwapMonthly} onChange={(e) => setNewSwapMonthly(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Daily Subscription (৳)</label>
|
||||
<input type="number" value={newSwapDaily} onChange={(e) => setNewSwapDaily(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Min Batteries</label>
|
||||
<input type="number" value={newSwapMin} onChange={(e) => setNewSwapMin(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Max Batteries</label>
|
||||
<input type="number" value={newSwapMax} onChange={(e) => setNewSwapMax(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Profit Share (%)</label>
|
||||
<input type="number" value={newSwapProfit} onChange={(e) => setNewSwapProfit(parseInt(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="text-sm text-slate-600">Description</label>
|
||||
<textarea value={newSwapDesc} onChange={(e) => setNewSwapDesc(e.target.value)} placeholder="Enter plan description" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" rows={2} />
|
||||
</div>
|
||||
<button onClick={createSwapStationPlan} className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium">Create Plan</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 border-b border-slate-200">
|
||||
{settings.plans.swapStation.map((plan, idx) => (
|
||||
<button key={idx} onClick={() => setActiveSwapTab(idx)} className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${activeSwapTab === idx ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700'}`}> {plan.name}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{settings.plans.swapStation.length > 0 && settings.plans.swapStation.map((plan, idx) => idx === activeSwapTab && (
|
||||
<div key={idx} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
<div className="bg-blue-50 px-4 py-3 border-b border-blue-100 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-800">{plan.name}</h4>
|
||||
<p className="text-sm text-blue-600 mt-1">{plan.description}</p>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${plan.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>{plan.status}</span>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Plan Condition</label>
|
||||
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Status</label>
|
||||
<select value={plan.status} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].status = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1">
|
||||
<option value="active">Active</option>
|
||||
<option value="paused">Paused</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Battery Count</label>
|
||||
<input type="number" value={plan.batteryCount} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].batteryCount = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Swap Price (৳)</label>
|
||||
<input type="number" value={plan.swapPrice} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].swapPrice = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Monthly Subscription (৳)</label>
|
||||
<input type="number" value={plan.monthlySubscription} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].monthlySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Daily Subscription (৳)</label>
|
||||
<input type="number" value={plan.dailySubscription} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].dailySubscription = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Min Batteries</label>
|
||||
<input type="number" value={plan.minBatteries} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].minBatteries = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Max Batteries</label>
|
||||
<input type="number" value={plan.maxBatteries} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].maxBatteries = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-slate-600">Profit Share (%)</label>
|
||||
<input type="number" value={plan.profitSharePercent} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].profitSharePercent = parseInt(e.target.value); setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label className="text-sm text-slate-600">Description</label>
|
||||
<textarea value={plan.description} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].description = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" rows={2} />
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button onClick={handleSave} className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium flex items-center gap-2">
|
||||
<Save className="w-4 h-4" /> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { useState, use } from 'react';
|
||||
import { Users, ArrowLeft, Mail, Phone, MapPin, Shield, Edit, Trash2, KeyRound, Eye, EyeOff, Wallet, Bike, Zap, Store, CheckCircle, XCircle, Clock, Link2 } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
type UserRole = 'admin' | 'manager' | 'biker' | 'investor' | 'shop' | 'merchant';
|
||||
type UserRole = 'admin' | 'manager' | 'biker' | 'investor' | 'swapstation' | 'merchant';
|
||||
type UserStatus = 'active' | 'inactive' | 'suspended';
|
||||
|
||||
interface User {
|
||||
@@ -18,7 +18,7 @@ interface User {
|
||||
lastLogin: string;
|
||||
createdAt: string;
|
||||
linkedProfile?: {
|
||||
type: 'biker' | 'investor' | 'shop' | 'merchant' | 'swap-station';
|
||||
type: 'biker' | 'investor' | 'swapstation' | 'merchant';
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
@@ -40,7 +40,7 @@ const mockUser: User = {
|
||||
const mockUsers: User[] = [
|
||||
{ id: 'USR-003', name: 'Jamal Biker', email: 'jamal@biker.com', phone: '+8801912345678', role: 'biker', status: 'active', password: '********', lastLogin: '2024-02-15 18:00', createdAt: '2023-09-20', linkedProfile: { type: 'biker', id: 'BIK-001', name: 'Jamal Delivery' } },
|
||||
{ id: 'USR-004', name: 'Rahim Investor', email: 'rahim@investor.com', phone: '+8801512345678', role: 'investor', status: 'active', password: '********', lastLogin: '2024-02-14 12:00', createdAt: '2023-03-10', linkedProfile: { type: 'investor', id: 'INV-001', name: 'Rahim Investments' } },
|
||||
{ id: 'USR-005', name: 'Shop Owner', email: 'shop@owner.com', phone: '+8801612345678', role: 'shop', status: 'active', password: '********', lastLogin: '2024-02-15 14:30', createdAt: '2023-11-05', linkedProfile: { type: 'shop', id: 'SHP-001', name: 'Jaiben Shop' } },
|
||||
{ id: 'USR-005', name: 'Swap Station Owner', email: 'swapstation@owner.com', phone: '+8801612345678', role: 'swapstation', status: 'active', password: '********', lastLogin: '2024-02-15 14:30', createdAt: '2023-11-05', linkedProfile: { type: 'swapstation', id: 'SHP-001', name: 'Jaiben Swap Station' } },
|
||||
{ id: 'USR-006', name: 'Merchant User', email: 'merchant@jaiben.com', phone: '+8801412345678', role: 'merchant', status: 'active', password: '********', lastLogin: '2024-02-15 16:00', createdAt: '2023-08-15', linkedProfile: { type: 'merchant', id: 'MCH-001', name: 'Rahim Delivery Service' } },
|
||||
];
|
||||
|
||||
@@ -49,7 +49,7 @@ const roleColors: Record<UserRole, string> = {
|
||||
manager: 'bg-purple-100 text-purple-700',
|
||||
biker: 'bg-blue-100 text-blue-700',
|
||||
investor: 'bg-green-100 text-green-700',
|
||||
shop: 'bg-amber-100 text-amber-700',
|
||||
swapstation: 'bg-amber-100 text-amber-700',
|
||||
merchant: 'bg-cyan-100 text-cyan-700'
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ const roleLabels: Record<UserRole, string> = {
|
||||
manager: 'Manager',
|
||||
biker: 'Biker',
|
||||
investor: 'Investor',
|
||||
shop: 'Shop',
|
||||
swapstation: 'Swap Station',
|
||||
merchant: 'Merchant'
|
||||
};
|
||||
|
||||
@@ -282,7 +282,7 @@ export default function UserDetailPage({ params }: { params: Promise<{ id: strin
|
||||
<div className="flex items-center gap-3">
|
||||
{user.linkedProfile.type === 'biker' && <Bike className="w-5 h-5 text-blue-500" />}
|
||||
{user.linkedProfile.type === 'investor' && <Wallet className="w-5 h-5 text-green-500" />}
|
||||
{user.linkedProfile.type === 'shop' && <Store className="w-5 h-5 text-amber-500" />}
|
||||
{user.linkedProfile.type === 'swapstation' && <Store className="w-5 h-5 text-amber-500" />}
|
||||
{user.linkedProfile.type === 'merchant' && <Zap className="w-5 h-5 text-cyan-500" />}
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-700">{user.linkedProfile.name}</p>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState } from 'react';
|
||||
import { Users, Plus, Search, X, Eye, Edit, Trash2, Shield, Mail, Phone, MapPin, CheckCircle, XCircle } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
type UserRole = 'admin' | 'manager' | 'biker' | 'investor' | 'shop' | 'merchant';
|
||||
type UserRole = 'admin' | 'manager' | 'biker' | 'investor' | 'swapstation' | 'merchant';
|
||||
type UserStatus = 'active' | 'inactive' | 'suspended';
|
||||
|
||||
interface User {
|
||||
@@ -18,7 +18,7 @@ interface User {
|
||||
lastLogin: string;
|
||||
createdAt: string;
|
||||
linkedProfile?: {
|
||||
type: 'biker' | 'investor' | 'shop' | 'merchant' | 'swap-station';
|
||||
type: 'biker' | 'investor' | 'swapstation' | 'merchant';
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
@@ -29,7 +29,7 @@ const mockUsers: User[] = [
|
||||
{ id: 'USR-002', name: 'Karim Manager', email: 'karim@jaiben.com', phone: '+8801812345678', role: 'manager', status: 'active', lastLogin: '2024-02-16 09:15', createdAt: '2023-06-15' },
|
||||
{ id: 'USR-003', name: 'Jamal Biker', email: 'jamal@biker.com', phone: '+8801912345678', role: 'biker', status: 'active', lastLogin: '2024-02-15 18:00', createdAt: '2023-09-20', linkedProfile: { type: 'biker', id: 'BIK-001', name: 'Jamal Delivery' } },
|
||||
{ id: 'USR-004', name: 'Rahim Investor', email: 'rahim@investor.com', phone: '+8801512345678', role: 'investor', status: 'active', lastLogin: '2024-02-14 12:00', createdAt: '2023-03-10', linkedProfile: { type: 'investor', id: 'INV-001', name: 'Rahim Investments' } },
|
||||
{ id: 'USR-005', name: 'Shop Owner', email: 'shop@owner.com', phone: '+8801612345678', role: 'shop', status: 'active', lastLogin: '2024-02-15 14:30', createdAt: '2023-11-05', linkedProfile: { type: 'shop', id: 'SHP-001', name: 'Jaiben Shop' } },
|
||||
{ id: 'USR-005', name: 'Swap Station Owner', email: 'swapstation@owner.com', phone: '+8801612345678', role: 'swapstation', status: 'active', lastLogin: '2024-02-15 14:30', createdAt: '2023-11-05', linkedProfile: { type: 'swapstation', id: 'SHP-001', name: 'Jaiben Swap Station' } },
|
||||
{ id: 'USR-006', name: 'Merchant User', email: 'merchant@jaiben.com', phone: '+8801412345678', role: 'merchant', status: 'active', lastLogin: '2024-02-15 16:00', createdAt: '2023-08-15', linkedProfile: { type: 'merchant', id: 'MCH-001', name: 'Rahim Delivery Service' } },
|
||||
];
|
||||
|
||||
@@ -38,7 +38,7 @@ const roleColors: Record<UserRole, string> = {
|
||||
manager: 'bg-purple-100 text-purple-700',
|
||||
biker: 'bg-blue-100 text-blue-700',
|
||||
investor: 'bg-green-100 text-green-700',
|
||||
shop: 'bg-amber-100 text-amber-700',
|
||||
swapstation: 'bg-amber-100 text-amber-700',
|
||||
merchant: 'bg-cyan-100 text-cyan-700'
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ const roleLabels: Record<UserRole, string> = {
|
||||
manager: 'Manager',
|
||||
biker: 'Biker',
|
||||
investor: 'Investor',
|
||||
shop: 'Shop',
|
||||
swapstation: 'Swap Station',
|
||||
merchant: 'Merchant'
|
||||
};
|
||||
|
||||
@@ -195,7 +195,7 @@ export default function UsersPage() {
|
||||
<option value="manager">Manager</option>
|
||||
<option value="biker">Biker</option>
|
||||
<option value="investor">Investor</option>
|
||||
<option value="shop">Shop</option>
|
||||
<option value="swapstation">Swap Station</option>
|
||||
<option value="merchant">Merchant</option>
|
||||
</select>
|
||||
<select
|
||||
@@ -331,7 +331,7 @@ export default function UsersPage() {
|
||||
<option value="manager">Manager</option>
|
||||
<option value="biker">Biker</option>
|
||||
<option value="investor">Investor</option>
|
||||
<option value="shop">Shop</option>
|
||||
<option value="swapstation">Swap Station</option>
|
||||
<option value="merchant">Merchant</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ const demoUsers = [
|
||||
{ email: 'staff@jaiben.com', role: 'staff', label: 'Front Desk', icon: Users, color: 'bg-purple-500' },
|
||||
{ email: 'accountant@jaiben.com', role: 'accountant', label: 'Accountant', icon: Calculator, color: 'bg-green-500' },
|
||||
{ email: 'investor@email.com', role: 'investor', label: 'Investor', icon: Wallet, color: 'bg-amber-500' },
|
||||
// { email: 'swap@jaiben.com', role: 'swap-station', label: 'Swap Station', icon: Zap, color: 'bg-purple-500' },
|
||||
// { email: 'swap@jaiben.com', role: 'swapstation', label: 'Swap Station', icon: Zap, color: 'bg-purple-500' },
|
||||
];
|
||||
|
||||
export default function LoginPage() {
|
||||
@@ -42,7 +42,7 @@ export default function LoginPage() {
|
||||
accountant: ['dashboard.view', 'accounting.view', 'accounting.create', 'accounting.edit', 'accounting.delete'],
|
||||
investor: ['dashboard.view', 'kyc.request', 'kyc.view'],
|
||||
biker: ['dashboard.view', 'kyc.request', 'kyc.view', 'rentals.view', 'rentals.create'],
|
||||
'swap-station': ['dashboard.view', 'kyc.request', 'kyc.view'],
|
||||
'swapstation': ['dashboard.view', 'kyc.request', 'kyc.view'],
|
||||
merchant: ['dashboard.view', 'kyc.request', 'kyc.view', 'merchants.view'],
|
||||
};
|
||||
sessionStorage.setItem('userPermissions', JSON.stringify(rolePerms[user.role] || []));
|
||||
@@ -62,7 +62,7 @@ export default function LoginPage() {
|
||||
case 'biker':
|
||||
router.push('/biker');
|
||||
break;
|
||||
case 'swap-station':
|
||||
case 'swapstation':
|
||||
router.push('/swapstation');
|
||||
break;
|
||||
case 'merchant':
|
||||
@@ -95,7 +95,7 @@ export default function LoginPage() {
|
||||
accountant: ['dashboard.view', 'accounting.view', 'accounting.create', 'accounting.edit', 'accounting.delete'],
|
||||
investor: ['dashboard.view', 'kyc.request', 'kyc.view'],
|
||||
biker: ['dashboard.view', 'kyc.request', 'kyc.view', 'rentals.view', 'rentals.create'],
|
||||
'swap-station': ['dashboard.view', 'kyc.request', 'kyc.view'],
|
||||
'swapstation': ['dashboard.view', 'kyc.request', 'kyc.view'],
|
||||
merchant: ['dashboard.view', 'kyc.request', 'kyc.view', 'merchants.view'],
|
||||
};
|
||||
sessionStorage.setItem('userPermissions', JSON.stringify(rolePerms[user.role] || []));
|
||||
@@ -115,7 +115,7 @@ export default function LoginPage() {
|
||||
case 'biker':
|
||||
router.push('/biker');
|
||||
break;
|
||||
case 'swap-station':
|
||||
case 'swapstation':
|
||||
router.push('/swapstation');
|
||||
break;
|
||||
case 'merchant':
|
||||
|
||||
Reference in New Issue
Block a user