Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 33 additions & 42 deletions RestroHub-FrontEnd/src/components/admin/store/tables/TableCard.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { QrCode, Edit2, Trash2, Users, Loader2 } from 'lucide-react';
import api from '@services/common/api';

const TableCard = ({ table, onShowQR, onEdit, onDelete }) => {
const [deleting, setDeleting] = useState(false);
const [saving, setSaving] = useState(false);

const statusStyles = {
available: {
Expand All @@ -23,38 +24,43 @@ const TableCard = ({ table, onShowQR, onEdit, onDelete }) => {
dot: 'bg-yellow-500',
numberBg: 'bg-yellow-50',
},
inactive: {
border: 'border-gray-200 hover:border-gray-300',
badge: 'bg-gray-100 text-gray-600',
dot: 'bg-gray-400',
numberBg: 'bg-gray-50',
},
};

const styles = statusStyles[table.status] || statusStyles.available;
const displayStatus = table.isActive ? table.status : 'inactive';
const styles = statusStyles[displayStatus] || statusStyles.available;

const handleDelete = async (e) => {
e.stopPropagation();
if (!window.confirm(`Delete Table ${table.number}?`)) return;

try {
setDeleting(true);
// 🔌 await api.delete(`/api/tables/${table.id}`);
await new Promise((r) => setTimeout(r, 300));
setSaving(true);
await api.delete(`/secure/api/v1/tables/${table.id}`);
onDelete(table.id);
} catch (err) {
console.error('Delete failed:', err);
alert(err.response?.data?.message || 'Failed to delete table');
} finally {
setDeleting(false);
setSaving(false);
}
};

return (
<div
onClick={() => onShowQR(table)}
onClick={() => table.isActive && onShowQR(table)}
className={`
cursor-pointer overflow-hidden rounded-2xl border-2
bg-white transition-all duration-200
hover:shadow-lg
overflow-hidden rounded-2xl border-2 bg-white transition-all duration-200
${table.isActive ? 'cursor-pointer hover:shadow-lg' : 'opacity-75'}
${styles.border}
`}
>
{/* Body */}
<div className="px-3 py-4 text-center sm:px-4 sm:py-5">
{/* Number Circle */}
<div
className={`
mx-auto mb-2 flex items-center justify-center
Expand All @@ -68,18 +74,15 @@ const TableCard = ({ table, onShowQR, onEdit, onDelete }) => {
</span>
</div>

{/* Label */}
<p className="text-xs font-semibold text-gray-900 sm:text-sm">
Table {table.number}
</p>

{/* Capacity */}
<div className="mt-1 flex items-center justify-center gap-1 text-gray-600">
<Users className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
<span className="text-xs sm:text-sm">{table.capacity} seats</span>
</div>

{/* Status Badge */}
<div className="mt-2 flex items-center justify-center">
<span
className={`
Expand All @@ -89,47 +92,35 @@ const TableCard = ({ table, onShowQR, onEdit, onDelete }) => {
`}
>
<span className={`inline-block h-1.5 w-1.5 rounded-full ${styles.dot}`} />
{table.status}
{displayStatus}
</span>
</div>
</div>

{/* Actions Footer */}
<div className="flex items-center justify-center gap-1 border-t border-gray-100 px-2 py-2 sm:gap-2 sm:px-3 sm:py-3">
<button
onClick={(e) => { e.stopPropagation(); onShowQR(table); }}
className="
inline-flex h-8 w-8 items-center justify-center
rounded-lg bg-blue-50 text-blue-600
hover:bg-blue-100 transition-colors
"
title="View QR"
>
<QrCode className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
</button>
{table.isActive && (
<button
onClick={(e) => { e.stopPropagation(); onShowQR(table); }}
className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 transition-colors hover:bg-blue-100"
title="View QR"
>
<QrCode className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
</button>
)}
<button
onClick={(e) => { e.stopPropagation(); onEdit?.(table); }}
className="
inline-flex h-8 w-8 items-center justify-center
rounded-lg bg-gray-50 text-gray-600
hover:bg-gray-100 transition-colors
"
className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-gray-50 text-gray-600 transition-colors hover:bg-gray-100"
title="Edit"
>
<Edit2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
</button>
<button
onClick={handleDelete}
disabled={deleting}
className="
inline-flex h-8 w-8 items-center justify-center
rounded-lg bg-red-50 text-red-600
hover:bg-red-100 transition-colors
disabled:opacity-50
"
disabled={saving}
className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-red-50 text-red-600 transition-colors hover:bg-red-100 disabled:opacity-50"
title="Delete"
>
{deleting ? (
{saving ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Trash2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
Expand All @@ -140,4 +131,4 @@ const TableCard = ({ table, onShowQR, onEdit, onDelete }) => {
);
};

export default TableCard;
export default TableCard;
Original file line number Diff line number Diff line change
@@ -1,26 +1,65 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { X, Loader2, LayoutGrid } from 'lucide-react';
import { Dialog } from '@headlessui/react';
import api from '@services/common/api';

const TableFormModal = ({ isOpen, onClose, branchId }) => {
const [formData, setFormData] = useState({ number: '', capacity: '' });
const TableFormModal = ({ isOpen, onClose, onSaved, branchId, editingTable }) => {
const [formData, setFormData] = useState({
number: '',
capacity: '',
status: 'available',
isActive: true,
});
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
if (editingTable) {
setFormData({
number: editingTable.number || '',
capacity: editingTable.capacity || '',
status: editingTable.status || 'available',
isActive: editingTable.isActive !== false,
});
} else {
setFormData({ number: '', capacity: '', status: 'available', isActive: true });
}
setError(null);
}, [editingTable, isOpen]);

const handleSubmit = async (e) => {
e.preventDefault();
setSubmitting(true);
setError(null);

try {
setSubmitting(true);
// 🔌 API call
await new Promise((r) => setTimeout(r, 500));
const payload = {
tableNumber: Number(formData.number),
capacity: Number(formData.capacity),
status: formData.status,
isActive: formData.isActive,
};

if (editingTable) {
await api.put(`/secure/api/v1/tables/${editingTable.id}`, payload);
} else {
await api.post(`/secure/api/v1/branches/${branchId}/tables`, payload);
}

onSaved?.();
onClose();
setFormData({ number: '', capacity: '' });
} catch (err) {
console.error('Failed:', err);
setError(err.response?.data?.message || 'Failed to save table');
} finally {
setSubmitting(false);
}
};

const updateField = (field, value) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};

const inputClass = `
w-full rounded-lg border border-gray-200 bg-white
px-4 py-2.5 text-sm text-gray-900 placeholder-gray-400
Expand All @@ -38,14 +77,13 @@ const TableFormModal = ({ isOpen, onClose, branchId }) => {
border border-gray-200 bg-white shadow-xl
"
>
{/* Header */}
<div className="flex items-center justify-between border-b border-gray-100 px-5 py-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-50">
<LayoutGrid className="h-5 w-5 text-blue-600" />
</div>
<Dialog.Title className="text-lg font-bold text-gray-900">
Add New Table
{editingTable ? 'Edit Table' : 'Add New Table'}
</Dialog.Title>
</div>
<button
Expand All @@ -59,17 +97,23 @@ const TableFormModal = ({ isOpen, onClose, branchId }) => {
</button>
</div>

{/* Form */}
<form onSubmit={handleSubmit}>
<div className="space-y-4 px-5 py-5">
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
{error}
</div>
)}

<div>
<label className="mb-1.5 block text-sm font-medium text-gray-800">
Table Number
</label>
<input
type="number"
min="1"
value={formData.number}
onChange={(e) => setFormData({ ...formData, number: e.target.value })}
onChange={(e) => updateField('number', e.target.value)}
className={inputClass}
placeholder="9"
required
Expand All @@ -81,16 +125,45 @@ const TableFormModal = ({ isOpen, onClose, branchId }) => {
</label>
<input
type="number"
min="1"
value={formData.capacity}
onChange={(e) => setFormData({ ...formData, capacity: e.target.value })}
onChange={(e) => updateField('capacity', e.target.value)}
className={inputClass}
placeholder="4"
required
/>
</div>
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-800">
Status
</label>
<select
value={formData.status}
onChange={(e) => updateField('status', e.target.value)}
className={inputClass}
>
<option value="available">Available</option>
<option value="occupied">Occupied</option>
<option value="reserved">Reserved</option>
</select>
</div>
{editingTable && (
<label className="flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
<span className="text-sm font-medium text-gray-800">Active table</span>
<span className="relative inline-flex items-center">
<input
type="checkbox"
checked={formData.isActive}
onChange={(e) => updateField('isActive', e.target.checked)}
className="peer sr-only"
/>
<span className="h-6 w-11 rounded-full bg-gray-300 transition-colors peer-checked:bg-blue-600" />
<span className="absolute left-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform peer-checked:translate-x-5" />
</span>
</label>
)}
</div>

{/* Footer */}
<div className="flex items-center gap-3 border-t border-gray-100 px-5 py-4">
<button
type="button"
Expand All @@ -116,7 +189,7 @@ const TableFormModal = ({ isOpen, onClose, branchId }) => {
"
>
{submitting && <Loader2 className="h-4 w-4 animate-spin" />}
Add Table
{editingTable ? 'Update Table' : 'Add Table'}
</button>
</div>
</form>
Expand All @@ -126,4 +199,4 @@ const TableFormModal = ({ isOpen, onClose, branchId }) => {
);
};

export default TableFormModal;
export default TableFormModal;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const TableQRModal = ({ isOpen, onClose, table, branchId }) => {

if (!table) return null;

const qrUrl = `${window.location.origin}/Restrohub/RajkotDhaba/${branchId}?table=${table.number}`;
const qrUrl = `${window.location.origin}/Restrohub/RajkotDhaba/${branchId}?tableId=${table.id}&table=${table.number}`;

const handleDownload = async () => {
try {
Expand All @@ -35,7 +35,7 @@ const TableQRModal = ({ isOpen, onClose, table, branchId }) => {
{/* Header */}
<div className="flex items-center justify-between border-b border-gray-100 px-5 py-4">
<Dialog.Title className="text-lg font-bold text-gray-900">
Table {table.number} QR Code
Table {table.number} - QR Code
</Dialog.Title>
<button
onClick={onClose}
Expand Down Expand Up @@ -114,4 +114,4 @@ const TableQRModal = ({ isOpen, onClose, table, branchId }) => {
);
};

export default TableQRModal;
export default TableQRModal;
Loading