feat: add inline editing and EV model selection to plan management components
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Plus, Save, Trash2, X } from 'lucide-react';
|
import { Plus, Save, Trash2, X, Pencil, Check } from 'lucide-react';
|
||||||
import { CompanySettings } from '../page';
|
import { CompanySettings } from '../page';
|
||||||
|
|
||||||
|
const EV_MODELS = ['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'];
|
||||||
|
|
||||||
interface PlanSelectionProps {
|
interface PlanSelectionProps {
|
||||||
settings: CompanySettings;
|
settings: CompanySettings;
|
||||||
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
setSettings: React.Dispatch<React.SetStateAction<CompanySettings>>;
|
||||||
@@ -11,6 +13,7 @@ interface PlanSelectionProps {
|
|||||||
setActivePlanTab: (tab: 'singleRent' | 'rentToOwn' | 'shareEv') => void;
|
setActivePlanTab: (tab: 'singleRent' | 'rentToOwn' | 'shareEv') => void;
|
||||||
handleSave: () => void;
|
handleSave: () => void;
|
||||||
addNewPlan: (type: 'singleRent' | 'rentToOwn' | 'shareEv') => void;
|
addNewPlan: (type: 'singleRent' | 'rentToOwn' | 'shareEv') => void;
|
||||||
|
isDirty: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PlanSelection({
|
export default function PlanSelection({
|
||||||
@@ -20,8 +23,19 @@ export default function PlanSelection({
|
|||||||
setActivePlanTab,
|
setActivePlanTab,
|
||||||
handleSave,
|
handleSave,
|
||||||
addNewPlan,
|
addNewPlan,
|
||||||
|
isDirty,
|
||||||
}: PlanSelectionProps) {
|
}: PlanSelectionProps) {
|
||||||
const [deleteModal, setDeleteModal] = useState<{ type: 'singleRent' | 'rentToOwn' | 'shareEv' | null; idx: number | null }>({ type: null, idx: null });
|
const [deleteModal, setDeleteModal] = useState<{ type: 'singleRent' | 'rentToOwn' | 'shareEv' | null; idx: number | null }>({ type: null, idx: null });
|
||||||
|
const [editingPlanId, setEditingPlanId] = useState<string | null>(null);
|
||||||
|
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const isAnyEditing = editingPlanId !== null;
|
||||||
|
const colorMap: Record<string, string> = {
|
||||||
|
singleRent: 'blue',
|
||||||
|
rentToOwn: 'purple',
|
||||||
|
shareEv: 'green',
|
||||||
|
};
|
||||||
|
const color = colorMap[activePlanTab] || 'blue';
|
||||||
|
|
||||||
const handleDeletePlan = () => {
|
const handleDeletePlan = () => {
|
||||||
if (deleteModal.type !== null && deleteModal.idx !== null) {
|
if (deleteModal.type !== null && deleteModal.idx !== null) {
|
||||||
@@ -31,11 +45,278 @@ export default function PlanSelection({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderPlanCard = (plan: CompanySettings['plans']['singleRent'][number] | CompanySettings['plans']['rentToOwn'][number] | CompanySettings['plans']['shareEv'][number], idx: number, type: 'singleRent' | 'rentToOwn' | 'shareEv') => {
|
||||||
|
const isEditing = editingPlanId === plan.id;
|
||||||
|
const isColor = color;
|
||||||
|
const accent = {
|
||||||
|
blue: { bg: 'bg-blue-600', bgLight: 'bg-blue-50', border: 'border-blue-100', text: 'text-blue-600', textDark: 'text-blue-800', textLight: 'text-blue-100', placeholder: 'placeholder-blue-400' },
|
||||||
|
purple: { bg: 'bg-purple-600', bgLight: 'bg-purple-50', border: 'border-purple-100', text: 'text-purple-600', textDark: 'text-purple-800', textLight: 'text-purple-100', placeholder: 'placeholder-purple-400' },
|
||||||
|
green: { bg: 'bg-green-600', bgLight: 'bg-green-50', border: 'border-green-100', text: 'text-green-600', textDark: 'text-green-800', textLight: 'text-green-100', placeholder: 'placeholder-green-400' },
|
||||||
|
}[isColor]!;
|
||||||
|
|
||||||
|
const inputClass = `w-full px-3 py-2 border border-slate-200 rounded-lg text-sm transition-all ${isEditing ? 'bg-white focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 outline-none' : 'bg-slate-50 text-slate-500 cursor-not-allowed'}`;
|
||||||
|
|
||||||
|
const updatePlan = (field: string, value: unknown) => {
|
||||||
|
const arr = [...(settings.plans[type] as unknown as Record<string, unknown>[])];
|
||||||
|
arr[idx] = { ...arr[idx], [field]: value };
|
||||||
|
setSettings({ ...settings, plans: { ...settings.plans, [type]: arr } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleEvModel = (model: string) => {
|
||||||
|
const current = plan.evModels as string[];
|
||||||
|
const updated = current.includes(model) ? current.filter(m => m !== model) : [...current, model];
|
||||||
|
updatePlan('evModels', updated);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isShareEv = type === 'shareEv';
|
||||||
|
const isRentToOwn = type === 'rentToOwn';
|
||||||
|
const depositLabel = isShareEv ? 'Deposit Each (৳)' : 'Deposit (৳)';
|
||||||
|
const planAny = plan as Record<string, unknown>;
|
||||||
|
const depositValue = isShareEv ? (planAny.depositEach as number) : (planAny.deposit as number);
|
||||||
|
const dailyRentLabel = isShareEv ? 'Daily Rent Each (৳)' : 'Daily Rent (৳)';
|
||||||
|
const dailyRentField = isShareEv ? (planAny.dailyRentEach as number) : (planAny.dailyRent as number);
|
||||||
|
|
||||||
|
const closeAllDropdowns = () => setOpenDropdown(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={plan.id} className={`bg-white rounded-xl border overflow-hidden transition-all ${isEditing ? `border-${isColor}-300 shadow-lg shadow-${isColor}-500/5 ring-1 ring-${isColor}-100` : 'border-slate-200 shadow-sm'}`}>
|
||||||
|
<div className={`${accent.bgLight} ${accent.border} px-4 py-3 border-b flex items-center justify-between`}>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h4 className={`font-semibold ${accent.textDark} truncate`}>{plan.name || 'Untitled Plan'} — ৳{dailyRentField}/day</h4>
|
||||||
|
<textarea
|
||||||
|
disabled={!isEditing}
|
||||||
|
value={plan.description}
|
||||||
|
onChange={(e) => updatePlan('description', e.target.value)}
|
||||||
|
className={`w-full bg-transparent text-sm ${accent.text} mt-1 border-0 resize-none p-0 focus:ring-0 leading-snug`}
|
||||||
|
rows={1}
|
||||||
|
placeholder="Plan description..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 ml-3 flex-shrink-0">
|
||||||
|
{!isEditing ? (
|
||||||
|
<button
|
||||||
|
onClick={() => setEditingPlanId(plan.id)}
|
||||||
|
className={`p-2 ${accent.bgLight} ${accent.text} rounded-lg hover:${accent.bg}/10 transition-all`}
|
||||||
|
title="Edit"
|
||||||
|
>
|
||||||
|
<Pencil className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => { handleSave(); setEditingPlanId(null); }}
|
||||||
|
disabled={!isDirty}
|
||||||
|
className={`px-3 py-1.5 ${accent.bg} text-white rounded-lg text-xs font-medium flex items-center gap-1 hover:${accent.bg}/90 transition-all ${!isDirty ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
|
title={isDirty ? 'Save changes' : 'No changes to save'}
|
||||||
|
>
|
||||||
|
<Save className="w-3.5 h-3.5" /> Save
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setEditingPlanId(null)}
|
||||||
|
className="px-3 py-1.5 bg-slate-100 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-200 transition-all"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => setDeleteModal({ type, idx })}
|
||||||
|
className={`p-2 ${accent.bgLight} text-red-500 rounded-lg hover:bg-red-50 transition-all`}
|
||||||
|
title="Delete"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-5 space-y-5">
|
||||||
|
<div className="grid grid-cols-1 md: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"
|
||||||
|
disabled={!isEditing}
|
||||||
|
value={plan.name}
|
||||||
|
onChange={(e) => updatePlan('name', e.target.value)}
|
||||||
|
className={inputClass}
|
||||||
|
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"
|
||||||
|
disabled={!isEditing}
|
||||||
|
onClick={() => {
|
||||||
|
if (isEditing) {
|
||||||
|
closeAllDropdowns();
|
||||||
|
setOpenDropdown(openDropdown === `ev-${plan.id}` ? null : `ev-${plan.id}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`${inputClass} text-left flex items-center justify-between ${isEditing ? 'cursor-pointer' : 'cursor-default'}`}
|
||||||
|
>
|
||||||
|
<span className={plan.evModels.length > 0 ? 'text-slate-800 truncate' : 'text-slate-400'}>
|
||||||
|
{plan.evModels.length > 0 ? `${plan.evModels.length} selected` : 'Select EV Models...'}
|
||||||
|
</span>
|
||||||
|
<svg className={`w-4 h-4 text-slate-400 flex-shrink-0 ml-1 ${isEditing ? '' : 'hidden'}`} 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>
|
||||||
|
{isEditing && openDropdown === `ev-${plan.id}` && (
|
||||||
|
<div className="absolute z-50 w-full mt-1 bg-white border border-slate-200 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
||||||
|
{EV_MODELS.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={() => toggleEvModel(model)}
|
||||||
|
className="rounded border-slate-300 text-blue-600"
|
||||||
|
/>
|
||||||
|
<span className="text-slate-700">{model}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{plan.evModels.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||||
|
{plan.evModels.map(model => (
|
||||||
|
<span key={model} className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-medium ${accent.bgLight} ${accent.text} border ${accent.border}`}>
|
||||||
|
{model}
|
||||||
|
{isEditing && (
|
||||||
|
<button onClick={() => toggleEvModel(model)} className="hover:opacity-70 ml-0.5">
|
||||||
|
<X className="w-2.5 h-2.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">{depositLabel}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
disabled={!isEditing}
|
||||||
|
value={depositValue}
|
||||||
|
onChange={(e) => updatePlan(isShareEv ? 'depositEach' : 'deposit', parseInt(e.target.value) || 0)}
|
||||||
|
className={inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{['dailyRent', 'weeklySubscription', 'monthlySubscription'].map((section) => {
|
||||||
|
const labelMap: Record<string, string> = {
|
||||||
|
dailyRent: isShareEv ? 'Daily Rent (Each)' : 'Daily Rent',
|
||||||
|
weeklySubscription: isShareEv ? 'Weekly Subscription (Each)' : 'Weekly Subscription',
|
||||||
|
monthlySubscription: isShareEv ? 'Monthly Subscription (Each)' : 'Monthly Subscription',
|
||||||
|
};
|
||||||
|
const baseField = isShareEv && section === 'dailyRent' ? 'dailyRentEach' : section === 'weeklySubscription' && isShareEv ? 'weeklySubscriptionEach' : section === 'monthlySubscription' && isShareEv ? 'monthlySubscriptionEach' : section;
|
||||||
|
const baseVal = plan[baseField as keyof typeof plan] as number;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={section} className="bg-slate-50 rounded-xl p-4 space-y-3">
|
||||||
|
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide">{labelMap[section]}</label>
|
||||||
|
<div className="grid grid-cols-4 gap-3">
|
||||||
|
<div>
|
||||||
|
<label className="text-xs text-slate-500">Base (৳)</label>
|
||||||
|
<input type="number" disabled={!isEditing} value={baseVal} onChange={(e) => updatePlan(baseField, parseInt(e.target.value) || 0)} className={inputClass} />
|
||||||
|
</div>
|
||||||
|
{[1, 2, 3].map(n => {
|
||||||
|
const pField = `${section.replace('Subscription', '')}Penalty${n}` as keyof typeof plan;
|
||||||
|
const penaltyVal = plan[pField] as number;
|
||||||
|
const labelMap2: Record<string, string> = { daily: 'Day', weekly: 'Day', monthly: 'Day' };
|
||||||
|
const prefix = section === 'dailyRent' ? 'daily' : section === 'weeklySubscription' ? 'weekly' : 'monthly';
|
||||||
|
const pFieldMap: Record<string, string> = { daily: `dailyRentPenalty${n}`, weekly: `weeklyPenalty${n}`, monthly: `monthlyPenalty${n}` };
|
||||||
|
return (
|
||||||
|
<div key={n}>
|
||||||
|
<label className="text-xs text-slate-500">{labelMap2[prefix]} {n} {n === 3 ? '+ Lock' : ''} (৳)</label>
|
||||||
|
<input type="number" disabled={!isEditing} value={penaltyVal} onChange={(e) => updatePlan(pFieldMap[prefix], parseInt(e.target.value) || 0)} className={inputClass} />
|
||||||
|
</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 => (
|
||||||
|
<div key={month} className={`px-3 py-1.5 rounded-lg text-xs font-medium flex items-center gap-1.5 transition-all ${isEditing ? `${accent.bg} text-white` : 'bg-slate-200 text-slate-600'}`}>
|
||||||
|
{month} {month === 1 ? 'Month' : 'Months'}
|
||||||
|
{isEditing && (
|
||||||
|
<button onClick={() => updatePlan('contractMonths', plan.contractMonths.filter(m => m !== month))} className="hover:opacity-70">
|
||||||
|
<X className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{isEditing && (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
placeholder="+"
|
||||||
|
className="w-14 px-2 py-1.5 border border-slate-200 rounded-lg text-xs text-center focus:ring-2 focus:ring-blue-100 outline-none"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const val = parseInt((e.target as HTMLInputElement).value);
|
||||||
|
if (val > 0 && !plan.contractMonths.includes(val)) {
|
||||||
|
updatePlan('contractMonths', [...plan.contractMonths, val].sort((a, b) => a - b));
|
||||||
|
(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)) {
|
||||||
|
updatePlan('contractMonths', [...plan.contractMonths, val].sort((a, b) => a - b));
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`px-2 py-1.5 ${accent.bg} text-white rounded-lg text-xs`}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</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
|
||||||
|
disabled={!isEditing}
|
||||||
|
value={plan.description}
|
||||||
|
onChange={(e) => updatePlan('description', e.target.value)}
|
||||||
|
className={inputClass}
|
||||||
|
rows={2}
|
||||||
|
placeholder="Enter plan description..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold text-slate-800">Plan Selection</h3>
|
<h3 className="text-lg font-semibold text-slate-800">Plan Selection</h3>
|
||||||
|
{isDirty && (
|
||||||
|
<span className="text-xs text-amber-500 font-medium flex items-center gap-1">
|
||||||
|
<div className="w-1.5 h-1.5 bg-amber-500 rounded-full animate-pulse"></div>
|
||||||
|
Unsaved changes
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 border-b border-slate-200">
|
<div className="flex gap-2 border-b border-slate-200">
|
||||||
@@ -46,138 +327,7 @@ export default function PlanSelection({
|
|||||||
|
|
||||||
{activePlanTab === 'singleRent' && (
|
{activePlanTab === 'singleRent' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{settings.plans.singleRent.map((plan, idx) => (
|
{settings.plans.singleRent.map((plan, idx) => renderPlanCard(plan, idx, 'singleRent'))}
|
||||||
<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">
|
<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
|
<Plus className="w-4 h-4" /> Add New Plan
|
||||||
</button>
|
</button>
|
||||||
@@ -186,138 +336,7 @@ export default function PlanSelection({
|
|||||||
|
|
||||||
{activePlanTab === 'rentToOwn' && (
|
{activePlanTab === 'rentToOwn' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{settings.plans.rentToOwn.map((plan, idx) => (
|
{settings.plans.rentToOwn.map((plan, idx) => renderPlanCard(plan, idx, 'rentToOwn'))}
|
||||||
<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">
|
<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
|
<Plus className="w-4 h-4" /> Add New Plan
|
||||||
</button>
|
</button>
|
||||||
@@ -326,144 +345,14 @@ export default function PlanSelection({
|
|||||||
|
|
||||||
{activePlanTab === 'shareEv' && (
|
{activePlanTab === 'shareEv' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{settings.plans.shareEv.map((plan, idx) => (
|
{settings.plans.shareEv.map((plan, idx) => renderPlanCard(plan, idx, 'shareEv'))}
|
||||||
<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">
|
<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
|
<Plus className="w-4 h-4" /> Add New Plan
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{deleteModal.type !== null && deleteModal.idx !== null && (
|
{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="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="bg-white rounded-xl shadow-xl w-full max-w-sm">
|
||||||
@@ -471,7 +360,7 @@ export default function PlanSelection({
|
|||||||
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
<h3 className="font-semibold text-slate-800 flex items-center gap-2">
|
||||||
<Trash2 className="w-5 h-5 text-red-500" /> Delete Plan
|
<Trash2 className="w-5 h-5 text-red-500" /> Delete Plan
|
||||||
</h3>
|
</h3>
|
||||||
<button onClick={() => setDeleteModal({ type: null, idx: null })} className="text-slate-400 hover:text-slate-600 text-2xl">×</button>
|
<button onClick={() => setDeleteModal({ type: null, idx: null })} className="text-slate-400 hover:text-slate-600 text-2xl leading-none">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<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>
|
<p className="text-sm text-slate-600">Are you sure you want to delete this plan? This action cannot be undone.</p>
|
||||||
@@ -482,7 +371,7 @@ export default function PlanSelection({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border-t border-slate-100 flex justify-end gap-2">
|
<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={() => setDeleteModal({ type: null, idx: null })} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">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>
|
<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>
|
</div>
|
||||||
|
|||||||
@@ -818,6 +818,9 @@ export default function CompanySettingsPage() {
|
|||||||
const [activeRentalTypeTab, setActiveRentalTypeTab] = useState<'single' | 'shared' | 'renttoown'>('single');
|
const [activeRentalTypeTab, setActiveRentalTypeTab] = useState<'single' | 'shared' | 'renttoown'>('single');
|
||||||
const [saved, setSaved] = useState(false);
|
const [saved, setSaved] = useState(false);
|
||||||
const [activePlanTab, setActivePlanTab] = useState<'singleRent' | 'rentToOwn' | 'shareEv'>('singleRent');
|
const [activePlanTab, setActivePlanTab] = useState<'singleRent' | 'rentToOwn' | 'shareEv'>('singleRent');
|
||||||
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
const [pendingTab, setPendingTab] = useState<typeof activeTab | null>(null);
|
||||||
|
const [showUnsavedModal, setShowUnsavedModal] = useState(false);
|
||||||
const [addDocType, setAddDocType] = useState<'investor' | 'merchant' | 'swapstation' | 'rental' | null>(null);
|
const [addDocType, setAddDocType] = useState<'investor' | 'merchant' | 'swapstation' | 'rental' | null>(null);
|
||||||
const [newDocName, setNewDocName] = useState('');
|
const [newDocName, setNewDocName] = useState('');
|
||||||
const [newDocDesc, setNewDocDesc] = useState('');
|
const [newDocDesc, setNewDocDesc] = useState('');
|
||||||
@@ -873,7 +876,7 @@ export default function CompanySettingsPage() {
|
|||||||
ficoShareEv: newInvestFicoShareEv,
|
ficoShareEv: newInvestFicoShareEv,
|
||||||
};
|
};
|
||||||
const updatedPlans = [...settings.plans.investment, newPlan];
|
const updatedPlans = [...settings.plans.investment, newPlan];
|
||||||
setSettings({ ...settings, plans: { ...settings.plans, investment: updatedPlans } });
|
updateSettings({ ...settings, plans: { ...settings.plans, investment: updatedPlans } });
|
||||||
setActiveInvestTab(updatedPlans.length - 1);
|
setActiveInvestTab(updatedPlans.length - 1);
|
||||||
setAddInvestPlan(false);
|
setAddInvestPlan(false);
|
||||||
setNewInvestName('');
|
setNewInvestName('');
|
||||||
@@ -909,7 +912,7 @@ export default function CompanySettingsPage() {
|
|||||||
description: newSwapDesc
|
description: newSwapDesc
|
||||||
};
|
};
|
||||||
const updatedPlans = [...settings.plans.swapStation, newPlan];
|
const updatedPlans = [...settings.plans.swapStation, newPlan];
|
||||||
setSettings({ ...settings, plans: { ...settings.plans, swapStation: updatedPlans } });
|
updateSettings({ ...settings, plans: { ...settings.plans, swapStation: updatedPlans } });
|
||||||
setActiveSwapTab(updatedPlans.length - 1);
|
setActiveSwapTab(updatedPlans.length - 1);
|
||||||
setAddSwapStationPlan(false);
|
setAddSwapStationPlan(false);
|
||||||
setNewSwapName('');
|
setNewSwapName('');
|
||||||
@@ -947,7 +950,7 @@ export default function CompanySettingsPage() {
|
|||||||
description: newRiderDesc
|
description: newRiderDesc
|
||||||
};
|
};
|
||||||
const updatedPlans = [...settings.plans.riderRequest, newPlan];
|
const updatedPlans = [...settings.plans.riderRequest, newPlan];
|
||||||
setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updatedPlans } });
|
updateSettings({ ...settings, plans: { ...settings.plans, riderRequest: updatedPlans } });
|
||||||
setActiveRiderTab(updatedPlans.length - 1);
|
setActiveRiderTab(updatedPlans.length - 1);
|
||||||
setAddRiderPlan(false);
|
setAddRiderPlan(false);
|
||||||
setNewRiderName('');
|
setNewRiderName('');
|
||||||
@@ -955,9 +958,24 @@ export default function CompanySettingsPage() {
|
|||||||
};
|
};
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setSaved(true);
|
setSaved(true);
|
||||||
|
setIsDirty(false);
|
||||||
setTimeout(() => setSaved(false), 2000);
|
setTimeout(() => setSaved(false), 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTabChange = (tab: typeof activeTab) => {
|
||||||
|
if (isDirty) {
|
||||||
|
setPendingTab(tab);
|
||||||
|
setShowUnsavedModal(true);
|
||||||
|
} else {
|
||||||
|
setActiveTab(tab);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSettings = (updater: React.SetStateAction<CompanySettings>) => {
|
||||||
|
setSettings(updater);
|
||||||
|
setIsDirty(true);
|
||||||
|
};
|
||||||
|
|
||||||
const addNewPlan = (type: 'singleRent' | 'rentToOwn' | 'shareEv') => {
|
const addNewPlan = (type: 'singleRent' | 'rentToOwn' | 'shareEv') => {
|
||||||
const newId = `${type}-${Date.now()}`;
|
const newId = `${type}-${Date.now()}`;
|
||||||
const defaultPlan = type === 'singleRent' ? {
|
const defaultPlan = type === 'singleRent' ? {
|
||||||
@@ -1034,23 +1052,24 @@ export default function CompanySettingsPage() {
|
|||||||
description: '',
|
description: '',
|
||||||
};
|
};
|
||||||
if (type === 'singleRent') {
|
if (type === 'singleRent') {
|
||||||
setSettings({ ...settings, plans: { ...settings.plans, singleRent: [...settings.plans.singleRent, defaultPlan as typeof settings.plans.singleRent[number]] } });
|
updateSettings({ ...settings, plans: { ...settings.plans, singleRent: [...settings.plans.singleRent, defaultPlan as typeof settings.plans.singleRent[number]] } });
|
||||||
} else if (type === 'rentToOwn') {
|
} else if (type === 'rentToOwn') {
|
||||||
setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: [...settings.plans.rentToOwn, defaultPlan as typeof settings.plans.rentToOwn[number]] } });
|
updateSettings({ ...settings, plans: { ...settings.plans, rentToOwn: [...settings.plans.rentToOwn, defaultPlan as typeof settings.plans.rentToOwn[number]] } });
|
||||||
} else {
|
} else {
|
||||||
setSettings({ ...settings, plans: { ...settings.plans, shareEv: [...settings.plans.shareEv, defaultPlan as typeof settings.plans.shareEv[number]] } });
|
updateSettings({ ...settings, plans: { ...settings.plans, shareEv: [...settings.plans.shareEv, defaultPlan as typeof settings.plans.shareEv[number]] } });
|
||||||
}
|
}
|
||||||
|
return newId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addPolicyRule = (tab: 'investor' | 'merchant' | 'swapstation') => {
|
const addPolicyRule = (tab: 'investor' | 'merchant' | 'swapstation') => {
|
||||||
if (!newPolicyName.trim()) return;
|
if (!newPolicyName.trim()) return;
|
||||||
const newRule = { title: newPolicyName, description: newPolicyDesc || '<p></p>' };
|
const newRule = { title: newPolicyName, description: newPolicyDesc || '<p></p>' };
|
||||||
if (tab === 'investor') {
|
if (tab === 'investor') {
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: [...settings.companyPolicy.investor, newRule] } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: [...settings.companyPolicy.investor, newRule] } });
|
||||||
} else if (tab === 'merchant') {
|
} else if (tab === 'merchant') {
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: [...settings.companyPolicy.merchant, newRule] } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: [...settings.companyPolicy.merchant, newRule] } });
|
||||||
} else if (tab === 'swapstation') {
|
} else if (tab === 'swapstation') {
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: [...settings.companyPolicy.swapStation, newRule] } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: [...settings.companyPolicy.swapStation, newRule] } });
|
||||||
}
|
}
|
||||||
setNewPolicyName('');
|
setNewPolicyName('');
|
||||||
setNewPolicyDesc('');
|
setNewPolicyDesc('');
|
||||||
@@ -1060,13 +1079,13 @@ export default function CompanySettingsPage() {
|
|||||||
const deletePolicyRule = (tab: 'investor' | 'merchant' | 'swapstation', index: number) => {
|
const deletePolicyRule = (tab: 'investor' | 'merchant' | 'swapstation', index: number) => {
|
||||||
if (tab === 'investor') {
|
if (tab === 'investor') {
|
||||||
const newRules = settings.companyPolicy.investor.filter((_, i) => i !== index);
|
const newRules = settings.companyPolicy.investor.filter((_, i) => i !== index);
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: newRules } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: newRules } });
|
||||||
} else if (tab === 'merchant') {
|
} else if (tab === 'merchant') {
|
||||||
const newRules = settings.companyPolicy.merchant.filter((_, i) => i !== index);
|
const newRules = settings.companyPolicy.merchant.filter((_, i) => i !== index);
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: newRules } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: newRules } });
|
||||||
} else if (tab === 'swapstation') {
|
} else if (tab === 'swapstation') {
|
||||||
const newRules = settings.companyPolicy.swapStation.filter((_, i) => i !== index);
|
const newRules = settings.companyPolicy.swapStation.filter((_, i) => i !== index);
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: newRules } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: newRules } });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1075,15 +1094,15 @@ export default function CompanySettingsPage() {
|
|||||||
if (tab === 'investor') {
|
if (tab === 'investor') {
|
||||||
const newRules = [...settings.companyPolicy.investor];
|
const newRules = [...settings.companyPolicy.investor];
|
||||||
newRules[index] = { title: editPolicyName, description: newDesc };
|
newRules[index] = { title: editPolicyName, description: newDesc };
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: newRules } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: newRules } });
|
||||||
} else if (tab === 'merchant') {
|
} else if (tab === 'merchant') {
|
||||||
const newRules = [...settings.companyPolicy.merchant];
|
const newRules = [...settings.companyPolicy.merchant];
|
||||||
newRules[index] = { title: editPolicyName, description: newDesc };
|
newRules[index] = { title: editPolicyName, description: newDesc };
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: newRules } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: newRules } });
|
||||||
} else if (tab === 'swapstation') {
|
} else if (tab === 'swapstation') {
|
||||||
const newRules = [...settings.companyPolicy.swapStation];
|
const newRules = [...settings.companyPolicy.swapStation];
|
||||||
newRules[index] = { title: editPolicyName, description: newDesc };
|
newRules[index] = { title: editPolicyName, description: newDesc };
|
||||||
setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: newRules } });
|
updateSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: newRules } });
|
||||||
}
|
}
|
||||||
setEditingPolicy(null);
|
setEditingPolicy(null);
|
||||||
};
|
};
|
||||||
@@ -1106,6 +1125,25 @@ export default function CompanySettingsPage() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{showUnsavedModal && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||||
|
<div className="bg-white rounded-xl shadow-2xl w-full max-w-sm overflow-hidden">
|
||||||
|
<div className="p-6 text-center">
|
||||||
|
<div className="w-14 h-14 bg-amber-50 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<Save className="w-7 h-7 text-amber-500" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-bold text-slate-800 mb-1">Unsaved Changes</h3>
|
||||||
|
<p className="text-sm text-slate-500">You have unsaved changes. What would you like to do?</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-slate-50 flex gap-3">
|
||||||
|
<button onClick={() => { setShowUnsavedModal(false); setPendingTab(null); }} className="flex-1 py-2.5 bg-white border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-slate-100 transition-colors">Keep Editing</button>
|
||||||
|
<button onClick={() => { setIsDirty(false); setShowUnsavedModal(false); setActiveTab(pendingTab!); setPendingTab(null); }} className="flex-1 py-2.5 bg-slate-600 text-white rounded-lg text-sm font-medium hover:bg-slate-700 transition-colors">Discard</button>
|
||||||
|
<button onClick={() => { handleSave(); setShowUnsavedModal(false); setActiveTab(pendingTab!); setPendingTab(null); }} className="flex-1 py-2.5 bg-accent text-white rounded-lg text-sm font-medium hover:bg-accent-dark transition-colors">Save & Switch</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="p-4 lg:p-6">
|
<div className="p-4 lg:p-6">
|
||||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6">
|
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -1132,7 +1170,7 @@ export default function CompanySettingsPage() {
|
|||||||
{tabs.map(tab => (
|
{tabs.map(tab => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => setActiveTab(tab.id as typeof activeTab)}
|
onClick={() => handleTabChange(tab.id as typeof activeTab)}
|
||||||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors ${activeTab === tab.id
|
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors ${activeTab === tab.id
|
||||||
? 'bg-accent text-white'
|
? 'bg-accent text-white'
|
||||||
: 'text-slate-600 hover:bg-slate-50'
|
: 'text-slate-600 hover:bg-slate-50'
|
||||||
@@ -1147,29 +1185,29 @@ export default function CompanySettingsPage() {
|
|||||||
|
|
||||||
<div className="lg:col-span-3 bg-white rounded-xl shadow-sm border border-slate-100">
|
<div className="lg:col-span-3 bg-white rounded-xl shadow-sm border border-slate-100">
|
||||||
{activeTab === 'general' && (
|
{activeTab === 'general' && (
|
||||||
<GeneralSettings settings={settings} setSettings={setSettings} />
|
<GeneralSettings settings={settings} setSettings={updateSettings} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'branding' && (
|
{activeTab === 'branding' && (
|
||||||
<BrandingSettings settings={settings} setSettings={setSettings} />
|
<BrandingSettings settings={settings} setSettings={updateSettings} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'social' && (
|
{activeTab === 'social' && (
|
||||||
<SocialSettings settings={settings} setSettings={setSettings} />
|
<SocialSettings settings={settings} setSettings={updateSettings} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'integration' && (
|
{activeTab === 'integration' && (
|
||||||
<IntegrationSettings settings={settings} setSettings={setSettings} />
|
<IntegrationSettings settings={settings} setSettings={updateSettings} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'landing' && (
|
{activeTab === 'landing' && (
|
||||||
<LandingSettings settings={settings} setSettings={setSettings} />
|
<LandingSettings settings={settings} setSettings={updateSettings} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'kyc' && (
|
{activeTab === 'kyc' && (
|
||||||
<KycSettings
|
<KycSettings
|
||||||
settings={settings}
|
settings={settings}
|
||||||
setSettings={setSettings}
|
setSettings={updateSettings}
|
||||||
activeMasterTab={activeMasterTab}
|
activeMasterTab={activeMasterTab}
|
||||||
setActiveMasterTab={setActiveMasterTab}
|
setActiveMasterTab={setActiveMasterTab}
|
||||||
addDocType={addDocType}
|
addDocType={addDocType}
|
||||||
@@ -1184,7 +1222,7 @@ export default function CompanySettingsPage() {
|
|||||||
{activeTab === 'parts' && (
|
{activeTab === 'parts' && (
|
||||||
<PartsSettings
|
<PartsSettings
|
||||||
settings={settings}
|
settings={settings}
|
||||||
setSettings={setSettings}
|
setSettings={updateSettings}
|
||||||
showAddPolicy={showAddPolicy}
|
showAddPolicy={showAddPolicy}
|
||||||
setShowAddPolicy={setShowAddPolicy}
|
setShowAddPolicy={setShowAddPolicy}
|
||||||
newPolicyName={newPolicyName}
|
newPolicyName={newPolicyName}
|
||||||
@@ -1205,7 +1243,7 @@ export default function CompanySettingsPage() {
|
|||||||
{activeTab === 'companyPolicy' && (
|
{activeTab === 'companyPolicy' && (
|
||||||
<CompanyPolicySettings
|
<CompanyPolicySettings
|
||||||
settings={settings}
|
settings={settings}
|
||||||
setSettings={setSettings}
|
setSettings={updateSettings}
|
||||||
activeMasterTab={activeMasterTab}
|
activeMasterTab={activeMasterTab}
|
||||||
setActiveMasterTab={setActiveMasterTab}
|
setActiveMasterTab={setActiveMasterTab}
|
||||||
activeRentalTypeTab={activeRentalTypeTab}
|
activeRentalTypeTab={activeRentalTypeTab}
|
||||||
@@ -1230,11 +1268,12 @@ export default function CompanySettingsPage() {
|
|||||||
{activeTab === 'plans' && (
|
{activeTab === 'plans' && (
|
||||||
<PlanSelection
|
<PlanSelection
|
||||||
settings={settings}
|
settings={settings}
|
||||||
setSettings={setSettings}
|
setSettings={updateSettings}
|
||||||
activePlanTab={activePlanTab}
|
activePlanTab={activePlanTab}
|
||||||
setActivePlanTab={setActivePlanTab}
|
setActivePlanTab={setActivePlanTab}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
addNewPlan={addNewPlan}
|
addNewPlan={addNewPlan}
|
||||||
|
isDirty={isDirty}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1310,5 +1349,6 @@ export default function CompanySettingsPage() {
|
|||||||
|
|
||||||
|
|
||||||
</div >
|
</div >
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user