diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx
index 3fbce8e..39b73db 100644
--- a/src/app/admin/settings/page.tsx
+++ b/src/app/admin/settings/page.tsx
@@ -1,7 +1,8 @@
'use client';
import { useState } from 'react';
-import { Settings, Upload, Image, Globe, Mail, MessageSquare, Phone, MapPin, Link2, Clock, Save, FileText, Camera, Palette, Ruler, Sun, Moon, Monitor, Smartphone, Tablet, Package, Wrench, FileCheck, BadgeDollarSign, CreditCard, Plus, X, DollarSign, Zap, Users } from 'lucide-react';
+import { Settings, Upload, Image, Globe, Mail, MessageSquare, Phone, MapPin, Link2, Clock, Save, FileText, Camera, Palette, Ruler, Sun, Moon, Monitor, Smartphone, Tablet, Package, Wrench, FileCheck, BadgeDollarSign, CreditCard, Plus, X, DollarSign, Zap, Users, Check, Pencil } from 'lucide-react';
+import RichTextEditor from '@/components/RichTextEditor';
interface CompanySettings {
name: string;
@@ -71,6 +72,12 @@ interface CompanySettings {
};
parts: { id: string; name: string; price?: number; minPrice?: number; maxPrice?: number; inStock: number }[];
serviceCenters: { id: string; name: string; address: string; phone: string; rating: number }[];
+ companyPolicy: {
+ investor: { title: string; description: string; rules: { name: string; description: string }[] };
+ merchant: { title: string; description: string; rules: { name: string; description: string }[] };
+ swapStation: { title: string; description: string; rules: { name: string; description: string }[] };
+ rentalTypes: { type: string; name: string; title: string; description: string; rules: { name: string; description: string }[]; enabled: boolean }[];
+ };
rentalPolicy: {
minAge: number;
requireLicense: boolean;
@@ -342,6 +349,64 @@ const initialSettings: CompanySettings = {
{ id: 'SC-001', name: 'JAIBEN Service Center - Gulshan', address: 'House 45, Road 13, Gulshan 1, Dhaka', phone: '+8801712345670', rating: 4.8 },
{ id: 'SC-002', name: 'JAIBEN Service Center - Banani', address: 'Road 11, Banani, Dhaka', phone: '+8801712345671', rating: 4.5 },
],
+ companyPolicy: {
+ investor: {
+ title: 'Investor Policy',
+ description: '
Investor Guidelines Welcome to our investor program. This document outlines the terms and conditions for all investors participating in our EV fleet sharing initiative.
Key Requirements All investors must complete KYC verification Minimum investment amount: ৳100,000 Monthly returns are calculated based on fleet utilization Note: Past performance does not guarantee future results. Please read all terms carefully before investing.
',
+ rules: [
+ { name: 'No smoking in vehicles', description: 'Smoking is strictly prohibited inside any vehicle in the fleet' },
+ { name: 'Return with same fuel/charge level', description: 'Vehicles must be returned with the same fuel/charge level as when rented' },
+ { name: 'No unauthorized drivers', description: 'Only authorized drivers listed in the agreement are permitted to drive' },
+ { name: 'Follow traffic rules', description: 'All traffic laws and regulations must be followed' },
+ { name: 'Report accidents within 24 hours', description: 'Any accident must be reported within 24 hours of occurrence' }
+ ]
+ },
+ merchant: {
+ title: 'Merchant Policy',
+ description: 'Merchant Terms & Conditions Thank you for joining our merchant network. These guidelines ensure smooth operations for all participating merchants.
Operational Requirements Maintain minimum inventory levels Provide excellent customer service Accept all payment methods offered Process orders within 24 hours Commission Structure Merchants receive 15% commission on each completed delivery plus monthly bonuses for high performance.
',
+ rules: [
+ { name: 'No smoking in vehicles', description: 'Smoking is strictly prohibited inside any vehicle in the fleet' },
+ { name: 'Return with same fuel/charge level', description: 'Vehicles must be returned with the same fuel/charge level as when rented' },
+ { name: 'No unauthorized drivers', description: 'Only authorized drivers listed in the agreement are permitted to drive' },
+ { name: 'Follow traffic rules', description: 'All traffic laws and regulations must be followed' },
+ { name: 'Report accidents within 24 hours', description: 'Any accident must be reported within 24 hours of occurrence' }
+ ]
+ },
+ swapStation: {
+ title: 'Swap Station Policy',
+ description: 'Swap Station Guidelines Welcome to our battery swap station network. Follow these safety and operational protocols for optimal service.
Safety Protocols Always wear protective gloves when handling batteries Inspect batteries for damage before swapping Keep swap station area clean and organized Report any malfunctioning equipment immediately Operating Hours Stations operate 24/7 for subscriber convenience. Emergency support available round the clock.
',
+ rules: [
+ { name: 'No smoking in vehicles', description: 'Smoking is strictly prohibited inside any vehicle in the fleet' },
+ { name: 'Return with same fuel/charge level', description: 'Vehicles must be returned with the same fuel/charge level as when rented' },
+ { name: 'No unauthorized drivers', description: 'Only authorized drivers listed in the agreement are permitted to drive' },
+ { name: 'Follow traffic rules', description: 'All traffic laws and regulations must be followed' },
+ { name: 'Report accidents within 24 hours', description: 'Any accident must be reported within 24 hours of occurrence' }
+ ]
+ },
+ rentalTypes: [
+ { type: 'single', name: 'Rental (Single)', title: 'Rental (Single)', description: 'Single Person Rental Perfect for individual riders who need a reliable vehicle for daily commute or delivery work.
Plan Features Daily, weekly, and monthly options available Comprehensive insurance included 24/7 roadside assistance Free maintenance during rental period Pricing Starting from ৳400/day with deposit of ৳5,000.
', rules: [
+ { name: 'No smoking in vehicles', description: 'Smoking is strictly prohibited inside any vehicle' },
+ { name: 'Return with same fuel/charge level', description: 'Vehicles must be returned with the same fuel/charge level' },
+ { name: 'No unauthorized drivers', description: 'Only the registered rider is permitted to drive' },
+ { name: 'Follow traffic rules', description: 'All traffic laws must be followed' },
+ { name: 'Report accidents within 24 hours', description: 'Any accident must be reported within 24 hours' }
+ ], enabled: true },
+ { type: 'shared', name: 'Rental (2 Person Shared)', title: 'Rental (2 Person Shared)', description: 'Shared Rental Plan Ideal for companions or delivery partners who want to share riding costs and responsibilities.
Plan Features Split costs between two riders Both users must be verified Shared liability coverage Flexible switch driver feature Pricing Starting from ৳600/day (৳300 each) with deposit of ৳8,000.
', rules: [
+ { name: 'No smoking in vehicles', description: 'Smoking is strictly prohibited inside any vehicle' },
+ { name: 'Return with same fuel/charge level', description: 'Vehicles must be returned with the same fuel/charge level' },
+ { name: 'No unauthorized drivers', description: 'Both registered riders are permitted to drive' },
+ { name: 'Follow traffic rules', description: 'All traffic laws must be followed' },
+ { name: 'Report accidents within 24 hours', description: 'Any accident must be reported within 24 hours' }
+ ], enabled: true },
+ { type: 'renttoown', name: 'Rent-to-Own', title: 'Rent-to-Own', description: 'Rent-to-Own Plan Build ownership gradually with our rent-to-own program. After completing the tenure, own the EV outright.
Program Benefits 50% of rental payments go toward purchase Option to buyout anytime Full ownership after 36 months Transferable to family members Requirements Good payment history required. Credit check applies.
', rules: [
+ { name: 'No smoking in vehicles', description: 'Smoking is strictly prohibited inside any vehicle' },
+ { name: 'Return with same fuel/charge level', description: 'Vehicles must be returned with the same fuel/charge level' },
+ { name: 'No unauthorized drivers', description: 'Only the registered rider is permitted to drive' },
+ { name: 'Follow traffic rules', description: 'All traffic laws must be followed' },
+ { name: 'Report accidents within 24 hours', description: 'Any accident must be reported within 24 hours' }
+ ], enabled: true },
+ ],
+ },
rentalPolicy: {
minAge: 18,
requireLicense: true,
@@ -603,14 +668,17 @@ const initialSettings: CompanySettings = {
export default function CompanySettingsPage() {
const [settings, setSettings] = useState(initialSettings);
- const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'parts' | 'rental' | 'plans' | 'investment' | 'swapstation' | 'riderrequest'>('general');
- const [activeMasterTab, setActiveMasterTab] = useState<'investor' | 'merchant' | 'swapstation' | 'rental'>('investor');
+ const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'parts' | 'companyPolicy' | 'plans' | 'investment' | 'swapstation' | 'riderrequest'>('general');
+ const [activeMasterTab, setActiveMasterTab] = useState<'investor' | 'merchant' | 'swapstation' | 'rental' | 'rentalType'>('investor');
const [saved, setSaved] = useState(false);
const [activePlanTab, setActivePlanTab] = useState<'singleRent' | 'rentToOwn' | 'shareEv'>('singleRent');
const [addDocType, setAddDocType] = useState<'investor' | 'merchant' | 'swapstation' | 'rental' | null>(null);
const [newDocName, setNewDocName] = useState('');
const [newDocDesc, setNewDocDesc] = useState('');
const [activeInvestTab, setActiveInvestTab] = useState(0);
+ const [editingPolicy, setEditingPolicy] = useState<{tab: string; index: number} | null>(null);
+ const [editPolicyName, setEditPolicyName] = useState('');
+ const [editPolicyDesc, setEditPolicyDesc] = useState('');
const [addInvestPlan, setAddInvestPlan] = useState(false);
const [newInvestName, setNewInvestName] = useState('');
const [newInvestTier, setNewInvestTier] = useState('Standard');
@@ -742,7 +810,7 @@ setNewSwapName('');
{ id: 'landing', label: 'Landing Page', icon: Monitor },
{ id: 'kyc', label: 'KYC Documents', icon: Package },
{ id: 'parts', label: 'EV Parts', icon: Package },
- { id: 'rental', label: 'Rental Policy', icon: FileCheck },
+ { id: 'companyPolicy', label: "Company's Policy", icon: FileCheck },
{ id: 'plans', label: 'Plan Selection', icon: Package },
{ id: 'investment', label: 'Investment Plan', icon: DollarSign },
{ id: 'swapstation', label: 'Swap Station Plan', icon: Zap },
@@ -1747,85 +1815,158 @@ setNewSwapName('');
)}
- {activeTab === 'rental' && (
+ {activeTab === 'companyPolicy' && (
-
Rental Policy
+
Company's Policy
-
-
- Minimum Age
- setSettings({ ...settings, rentalPolicy: { ...settings.rentalPolicy, minAge: parseInt(e.target.value) } })}
- className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
- />
-
-
- Security Deposit (৳)
- setSettings({ ...settings, rentalPolicy: { ...settings.rentalPolicy, deposit: parseInt(e.target.value) } })}
- className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
- />
-
-
- Late Fee per Hour (৳)
- setSettings({ ...settings, rentalPolicy: { ...settings.rentalPolicy, lateFeePerHour: parseInt(e.target.value) } })}
- className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
- />
-
-
- Cancellation Fee (৳)
- setSettings({ ...settings, rentalPolicy: { ...settings.rentalPolicy, cancellationFee: parseInt(e.target.value) } })}
- className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
- />
-
+
+ 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
+ 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
+ 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
+ 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
-
-
-
setSettings({ ...settings, rentalPolicy: { ...settings.rentalPolicy, requireLicense: e.target.checked } })}
- className="w-4 h-4"
- />
-
Require Driving License
+ {activeMasterTab === 'investor' && (
+
+
+ Policy Title
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: { title: e.target.value, description: settings.companyPolicy?.investor?.description || '', rules: settings.companyPolicy?.investor?.rules || [] } } })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
+
+
+ Policy Description
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, investor: { title: settings.companyPolicy?.investor?.title || '', description: val, rules: settings.companyPolicy?.investor?.rules || [] } } })} placeholder="Enter policy description..." minHeight={160} />
+
+
+ Save Changes
+
+
+
+ Policy List
+
+
+ {(settings.companyPolicy?.investor?.rules || []).map((policy, i) => (
+
+
+
+
+ {policy.name}
+
+
{policy.description}
+
+
+
+ ))}
+
+
-
+ )}
-
-
Damage Penalties
-
- {settings.rentalPolicy.damagePenalty.map((penalty, i) => (
-
-
{penalty.level}
-
৳{penalty.amount}
+ {activeMasterTab === 'merchant' && (
+
+
+ Policy Title
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: { title: e.target.value, description: settings.companyPolicy?.merchant?.description || '', rules: settings.companyPolicy?.merchant?.rules || [] } } })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
+
+
+ Policy Description
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, merchant: { title: settings.companyPolicy?.merchant?.title || '', description: val, rules: settings.companyPolicy?.merchant?.rules || [] } } })} placeholder="Enter policy description..." minHeight={160} />
+
+
+ Save Changes
+
+
+
+ Policy List
+
+
+ {(settings.companyPolicy?.merchant?.rules || []).map((policy, i) => (
+
+
+
+
+ {policy.name}
+
+
{policy.description}
+
+
+
+ ))}
+
+
+
+ )}
+
+ {activeMasterTab === 'swapstation' && (
+
+
+ Policy Title
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: { title: e.target.value, description: settings.companyPolicy?.swapStation?.description || '', rules: settings.companyPolicy?.swapStation?.rules || [] } } })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
+
+
+ Policy Description
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, swapStation: { title: settings.companyPolicy?.swapStation?.title || '', description: val, rules: settings.companyPolicy?.swapStation?.rules || [] } } })} placeholder="Enter policy description..." minHeight={160} />
+
+
+ Save Changes
+
+
+
+ Policy List
+
+
+ {(settings.companyPolicy?.swapStation?.rules || []).map((policy, i) => (
+
+
+
+
+ {policy.name}
+
+
{policy.description}
+
+
+
+ ))}
+
+
+
+ )}
+
+ {activeMasterTab === 'rentalType' && (
+
+ {(settings.companyPolicy?.rentalTypes || []).map((rtype, idx) => (
+
+
+
+ {
+ const updated = [...(settings.companyPolicy?.rentalTypes || [])];
+ updated[idx] = { ...updated[idx], enabled: e.target.checked };
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, rentalTypes: updated } });
+ }} className="w-4 h-4" />
+
{rtype.name}
+
+
+
+ {
+ const updated = [...(settings.companyPolicy?.rentalTypes || [])];
+ updated[idx] = { ...updated[idx], title: e.target.value };
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, rentalTypes: updated } });
+ }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Policy Title" />
+ {
+ const updated = [...(settings.companyPolicy?.rentalTypes || [])];
+ updated[idx] = { ...updated[idx], description: val };
+ setSettings({ ...settings, companyPolicy: { ...settings.companyPolicy, rentalTypes: updated } });
+ }} placeholder="Enter policy description..." minHeight={120} />
+
))}
+
+ Save Changes
+
-
-
-
-
Rental Rules
-
- {settings.rentalPolicy.rules.map((rule, i) => (
-
- {rule}
-
- ))}
-
-
+ )}
- )}
+)}
+
{activeTab === 'plans' && (
diff --git a/src/components/RichTextEditor.tsx b/src/components/RichTextEditor.tsx
new file mode 100644
index 0000000..097dc7e
--- /dev/null
+++ b/src/components/RichTextEditor.tsx
@@ -0,0 +1,258 @@
+'use client';
+
+import { useState, useRef, useEffect, useCallback, Fragment } from 'react';
+import { Bold, Italic, Underline, List, ListOrdered, AlignLeft, AlignCenter, AlignRight, Undo, Redo, Table, Trash2, Rows, Columns } from 'lucide-react';
+
+interface RichTextEditorProps {
+ value: string;
+ onChange: (value: string) => void;
+ placeholder?: string;
+ minHeight?: number;
+}
+
+export default function RichTextEditor({ value, onChange, placeholder = 'Enter description...', minHeight = 150 }: RichTextEditorProps) {
+ const editorRef = useRef
(null);
+ const [isInitialized, setIsInitialized] = useState(false);
+ const [showTableMenu, setShowTableMenu] = useState(false);
+ const lastValueRef = useRef(value);
+
+ useEffect(() => {
+ if (editorRef.current && !isInitialized) {
+ editorRef.current.innerHTML = value || '';
+ lastValueRef.current = value;
+ setIsInitialized(true);
+ }
+ }, [value, isInitialized]);
+
+ useEffect(() => {
+ if (editorRef.current && isInitialized && value !== lastValueRef.current) {
+ if (document.activeElement !== editorRef.current) {
+ editorRef.current.innerHTML = value || '';
+ lastValueRef.current = value;
+ }
+ }
+ }, [value, isInitialized]);
+
+ const execCommand = (command: string) => {
+ editorRef.current?.focus();
+
+ if (command === 'bold') {
+ document.execCommand('bold', false);
+ } else if (command === 'italic') {
+ document.execCommand('italic', false);
+ } else if (command === 'underline') {
+ document.execCommand('underline', false);
+ } else if (command === 'insertUnorderedList') {
+ document.execCommand('insertUnorderedList', false);
+ } else if (command === 'insertOrderedList') {
+ document.execCommand('insertOrderedList', false);
+ } else if (command === 'justifyLeft') {
+ document.execCommand('justifyLeft', false);
+ } else if (command === 'justifyCenter') {
+ document.execCommand('justifyCenter', false);
+ } else if (command === 'justifyRight') {
+ document.execCommand('justifyRight', false);
+ } else if (command === 'undo') {
+ document.execCommand('undo', false);
+ } else if (command === 'redo') {
+ document.execCommand('redo', false);
+ }
+
+ updateValue();
+ };
+
+ const updateValue = useCallback(() => {
+ if (editorRef.current) {
+ lastValueRef.current = editorRef.current.innerHTML;
+ onChange(editorRef.current.innerHTML);
+ }
+ }, [onChange]);
+
+ const insertTable = (rows: number, cols: number) => {
+ let tableHTML = '';
+ for (let r = 0; r < rows; r++) {
+ tableHTML += '';
+ for (let c = 0; c < cols; c++) {
+ const cellTag = r === 0 ? 'th' : 'td';
+ const border = 'border: 1px solid #ccc; padding: 8px;';
+ tableHTML += `<${cellTag} style="${border}"> ${cellTag}>`;
+ }
+ tableHTML += ' ';
+ }
+ tableHTML += '
';
+
+ if (editorRef.current) {
+ editorRef.current.focus();
+ document.execCommand('insertHTML', false, tableHTML);
+ updateValue();
+ }
+ setShowTableMenu(false);
+ };
+
+ const insertRow = () => {
+ if (!editorRef.current) return;
+ const selection = window.getSelection();
+ if (!selection || selection.rangeCount === 0) return;
+
+ const range = selection.getRangeAt(0);
+ let cell = range.commonAncestorContainer;
+ while (cell && cell !== editorRef.current) {
+ if ((cell as HTMLElement).tagName === 'TD' || (cell as HTMLElement).tagName === 'TH') {
+ const row = (cell as HTMLElement).parentElement;
+ if (row && row.tagName === 'TR') {
+ const newRow = row.cloneNode(true) as HTMLElement;
+ row.parentElement?.insertBefore(newRow, row.nextSibling);
+ updateValue();
+ break;
+ }
+ }
+ cell = (cell as HTMLElement).parentElement || (cell as Text).parentElement!;
+ }
+ };
+
+ const insertColumn = () => {
+ if (!editorRef.current) return;
+ const selection = window.getSelection();
+ if (!selection || selection.rangeCount === 0) return;
+
+ const range = selection.getRangeAt(0);
+ let cell = range.commonAncestorContainer;
+ while (cell && cell !== editorRef.current) {
+ if ((cell as HTMLElement).tagName === 'TD' || (cell as HTMLElement).tagName === 'TH') {
+ const table = (cell as HTMLElement).closest('table');
+ if (table) {
+ const colIndex = Array.from((cell as HTMLElement).parentElement?.children || []).indexOf(cell as HTMLElement);
+ table.querySelectorAll('tr').forEach(row => {
+ const newCell = (cell as HTMLElement).cloneNode() as HTMLElement;
+ newCell.innerHTML = ' ';
+ if (row.children[colIndex]) {
+ row.insertBefore(newCell, row.children[colIndex]);
+ } else {
+ row.appendChild(newCell);
+ }
+ });
+ updateValue();
+ break;
+ }
+ }
+ cell = (cell as HTMLElement).parentElement || (cell as Text).parentElement!;
+ }
+ };
+
+ const deleteTable = () => {
+ if (!editorRef.current) return;
+ const selection = window.getSelection();
+ if (!selection || selection.rangeCount === 0) return;
+
+ const range = selection.getRangeAt(0);
+ let cell = range.commonAncestorContainer;
+ while (cell && cell !== editorRef.current) {
+ if ((cell as HTMLElement).tagName === 'TABLE') {
+ (cell as HTMLElement).remove();
+ updateValue();
+ break;
+ }
+ cell = (cell as HTMLElement).parentElement || (cell as Text).parentElement!;
+ }
+ };
+
+ const ToolbarButton = ({ onClick, children, title }: { onClick: () => void; children: React.ReactNode; title: string }) => (
+
+ {children}
+
+ );
+
+ const ToolbarDivider = () =>
;
+
+ return (
+
+
+
execCommand('bold')} title="Bold">
+
+
+
execCommand('italic')} title="Italic">
+
+
+
execCommand('underline')} title="Underline">
+
+
+
+
execCommand('insertUnorderedList')} title="Bullet List">
+
+
+
execCommand('insertOrderedList')} title="Numbered List">
+
+
+
+
execCommand('justifyLeft')} title="Align Left">
+
+
+
execCommand('justifyCenter')} title="Align Center">
+
+
+
execCommand('justifyRight')} title="Align Right">
+
+
+
+
+
setShowTableMenu(!showTableMenu)} title="Insert Table">
+
+
+ {showTableMenu && (
+
+
Insert Table
+
+ {[2, 3, 4, 5].map(rows =>
+
+ {[2, 3, 4, 5].map(cols => (
+ insertTable(rows, cols)}
+ className="w-6 h-6 text-xs bg-slate-100 hover:bg-blue-100 rounded flex items-center justify-center"
+ >
+ {rows}x{cols}
+
+ ))}
+
+ )}
+
+
+
+ Add Row
+
+
+ Add Column
+
+
+ Delete Table
+
+
+
+ )}
+
+
+
execCommand('undo')} title="Undo">
+
+
+
execCommand('redo')} title="Redo">
+
+
+
+
+
+ );
+}
\ No newline at end of file