sorting in tables
This commit is contained in:
@@ -305,10 +305,28 @@ const AddShoppingEventModal: React.FC<AddShoppingEventModalProps> = ({
|
|||||||
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
<option value={0}>Select a product</option>
|
<option value={0}>Select a product</option>
|
||||||
{products.map(product => (
|
{Object.entries(
|
||||||
|
products.reduce((groups, product) => {
|
||||||
|
const category = product.grocery.category.name;
|
||||||
|
if (!groups[category]) {
|
||||||
|
groups[category] = [];
|
||||||
|
}
|
||||||
|
groups[category].push(product);
|
||||||
|
return groups;
|
||||||
|
}, {} as Record<string, typeof products>)
|
||||||
|
)
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
|
.map(([category, categoryProducts]) => (
|
||||||
|
<optgroup key={category} label={category}>
|
||||||
|
{categoryProducts
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
.map(product => (
|
||||||
<option key={product.id} value={product.id}>
|
<option key={product.id} value={product.id}>
|
||||||
{product.name}{product.organic ? '🌱' : ''} ({product.grocery.category.name}) {product.weight ? `${product.weight}${product.weight_unit}` : product.weight_unit}
|
{product.name}{product.organic ? '🌱' : ''}{product.brand ? ` (${product.brand.name})` : ''} {product.weight ? `${product.weight}${product.weight_unit}` : product.weight_unit}
|
||||||
</option>
|
</option>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</optgroup>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const BrandList: React.FC = () => {
|
|||||||
const [editingBrand, setEditingBrand] = useState<Brand | null>(null);
|
const [editingBrand, setEditingBrand] = useState<Brand | null>(null);
|
||||||
const [deletingBrand, setDeletingBrand] = useState<Brand | null>(null);
|
const [deletingBrand, setDeletingBrand] = useState<Brand | null>(null);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
const [sortField, setSortField] = useState<keyof Brand>('name');
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchBrands();
|
fetchBrands();
|
||||||
@@ -82,6 +84,58 @@ const BrandList: React.FC = () => {
|
|||||||
setDeletingBrand(null);
|
setDeletingBrand(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (field: keyof Brand) => {
|
||||||
|
if (field === sortField) {
|
||||||
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedBrands = [...brands].sort((a, b) => {
|
||||||
|
let aValue = a[sortField];
|
||||||
|
let bValue = b[sortField];
|
||||||
|
|
||||||
|
// Handle null/undefined values
|
||||||
|
if (aValue === null || aValue === undefined) aValue = '';
|
||||||
|
if (bValue === null || bValue === undefined) bValue = '';
|
||||||
|
|
||||||
|
// Convert to string for comparison
|
||||||
|
const aStr = String(aValue).toLowerCase();
|
||||||
|
const bStr = String(bValue).toLowerCase();
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aStr.localeCompare(bStr);
|
||||||
|
} else {
|
||||||
|
return bStr.localeCompare(aStr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSortIcon = (field: keyof Brand) => {
|
||||||
|
if (sortField !== field) {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-64">
|
<div className="flex justify-center items-center h-64">
|
||||||
@@ -121,14 +175,32 @@ const BrandList: React.FC = () => {
|
|||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('name')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Name
|
Name
|
||||||
|
{getSortIcon('name')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('created_at')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Created
|
Created
|
||||||
|
{getSortIcon('created_at')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('updated_at')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Updated
|
Updated
|
||||||
|
{getSortIcon('updated_at')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Actions
|
Actions
|
||||||
@@ -136,7 +208,7 @@ const BrandList: React.FC = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{brands.map((brand) => (
|
{sortedBrands.map((brand) => (
|
||||||
<tr key={brand.id} className="hover:bg-gray-50">
|
<tr key={brand.id} className="hover:bg-gray-50">
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const GroceryCategoryList: React.FC = () => {
|
|||||||
const [editingCategory, setEditingCategory] = useState<GroceryCategory | null>(null);
|
const [editingCategory, setEditingCategory] = useState<GroceryCategory | null>(null);
|
||||||
const [deletingCategory, setDeletingCategory] = useState<GroceryCategory | null>(null);
|
const [deletingCategory, setDeletingCategory] = useState<GroceryCategory | null>(null);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
const [sortField, setSortField] = useState<keyof GroceryCategory>('name');
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
@@ -72,6 +74,58 @@ const GroceryCategoryList: React.FC = () => {
|
|||||||
fetchCategories();
|
fetchCategories();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (field: keyof GroceryCategory) => {
|
||||||
|
if (field === sortField) {
|
||||||
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedCategories = [...categories].sort((a, b) => {
|
||||||
|
let aValue = a[sortField];
|
||||||
|
let bValue = b[sortField];
|
||||||
|
|
||||||
|
// Handle null/undefined values
|
||||||
|
if (aValue === null || aValue === undefined) aValue = '';
|
||||||
|
if (bValue === null || bValue === undefined) bValue = '';
|
||||||
|
|
||||||
|
// Convert to string for comparison
|
||||||
|
const aStr = String(aValue).toLowerCase();
|
||||||
|
const bStr = String(bValue).toLowerCase();
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aStr.localeCompare(bStr);
|
||||||
|
} else {
|
||||||
|
return bStr.localeCompare(aStr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSortIcon = (field: keyof GroceryCategory) => {
|
||||||
|
if (sortField !== field) {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-64">
|
<div className="flex justify-center items-center h-64">
|
||||||
@@ -115,11 +169,23 @@ const GroceryCategoryList: React.FC = () => {
|
|||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('name')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Name
|
Name
|
||||||
|
{getSortIcon('name')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('created_at')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Created
|
Created
|
||||||
|
{getSortIcon('created_at')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Actions
|
Actions
|
||||||
@@ -127,7 +193,7 @@ const GroceryCategoryList: React.FC = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{categories.map((category) => (
|
{sortedCategories.map((category) => (
|
||||||
<tr key={category.id} className="hover:bg-gray-50">
|
<tr key={category.id} className="hover:bg-gray-50">
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const GroceryList: React.FC = () => {
|
|||||||
const [editingGrocery, setEditingGrocery] = useState<Grocery | null>(null);
|
const [editingGrocery, setEditingGrocery] = useState<Grocery | null>(null);
|
||||||
const [deletingGrocery, setDeletingGrocery] = useState<Grocery | null>(null);
|
const [deletingGrocery, setDeletingGrocery] = useState<Grocery | null>(null);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
const [sortField, setSortField] = useState<string>('name');
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroceries();
|
fetchGroceries();
|
||||||
@@ -72,6 +74,76 @@ const GroceryList: React.FC = () => {
|
|||||||
fetchGroceries();
|
fetchGroceries();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (field: string) => {
|
||||||
|
if (field === sortField) {
|
||||||
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedGroceries = [...groceries].sort((a, b) => {
|
||||||
|
let aValue: any;
|
||||||
|
let bValue: any;
|
||||||
|
|
||||||
|
switch (sortField) {
|
||||||
|
case 'name':
|
||||||
|
aValue = a.name;
|
||||||
|
bValue = b.name;
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
aValue = a.category.name;
|
||||||
|
bValue = b.category.name;
|
||||||
|
break;
|
||||||
|
case 'created_at':
|
||||||
|
aValue = a.created_at;
|
||||||
|
bValue = b.created_at;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
aValue = '';
|
||||||
|
bValue = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle null/undefined values
|
||||||
|
if (aValue === null || aValue === undefined) aValue = '';
|
||||||
|
if (bValue === null || bValue === undefined) bValue = '';
|
||||||
|
|
||||||
|
// Convert to string for comparison
|
||||||
|
const aStr = String(aValue).toLowerCase();
|
||||||
|
const bStr = String(bValue).toLowerCase();
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aStr.localeCompare(bStr);
|
||||||
|
} else {
|
||||||
|
return bStr.localeCompare(aStr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSortIcon = (field: string) => {
|
||||||
|
if (sortField !== field) {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-64">
|
<div className="flex justify-center items-center h-64">
|
||||||
@@ -115,14 +187,32 @@ const GroceryList: React.FC = () => {
|
|||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('name')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Name
|
Name
|
||||||
|
{getSortIcon('name')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('category')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Category
|
Category
|
||||||
|
{getSortIcon('category')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('created_at')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Created
|
Created
|
||||||
|
{getSortIcon('created_at')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Actions
|
Actions
|
||||||
@@ -130,7 +220,7 @@ const GroceryList: React.FC = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{groceries.map((grocery) => (
|
{sortedGroceries.map((grocery) => (
|
||||||
<tr key={grocery.id} className="hover:bg-gray-50">
|
<tr key={grocery.id} className="hover:bg-gray-50">
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const ProductList: React.FC = () => {
|
|||||||
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
|
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
|
||||||
const [deletingProduct, setDeletingProduct] = useState<Product | null>(null);
|
const [deletingProduct, setDeletingProduct] = useState<Product | null>(null);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
const [sortField, setSortField] = useState<string>('name');
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProducts();
|
fetchProducts();
|
||||||
@@ -77,6 +79,92 @@ const ProductList: React.FC = () => {
|
|||||||
setDeletingProduct(null);
|
setDeletingProduct(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (field: string) => {
|
||||||
|
if (field === sortField) {
|
||||||
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedProducts = [...products].sort((a, b) => {
|
||||||
|
let aValue: any;
|
||||||
|
let bValue: any;
|
||||||
|
|
||||||
|
switch (sortField) {
|
||||||
|
case 'name':
|
||||||
|
aValue = a.name;
|
||||||
|
bValue = b.name;
|
||||||
|
break;
|
||||||
|
case 'grocery':
|
||||||
|
aValue = a.grocery.name;
|
||||||
|
bValue = b.grocery.name;
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
aValue = a.grocery.category.name;
|
||||||
|
bValue = b.grocery.category.name;
|
||||||
|
break;
|
||||||
|
case 'brand':
|
||||||
|
aValue = a.brand?.name || '';
|
||||||
|
bValue = b.brand?.name || '';
|
||||||
|
break;
|
||||||
|
case 'weight':
|
||||||
|
aValue = a.weight || 0;
|
||||||
|
bValue = b.weight || 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
aValue = '';
|
||||||
|
bValue = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle null/undefined values
|
||||||
|
if (aValue === null || aValue === undefined) aValue = '';
|
||||||
|
if (bValue === null || bValue === undefined) bValue = '';
|
||||||
|
|
||||||
|
// Convert to string for comparison (except for numbers)
|
||||||
|
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aValue - bValue;
|
||||||
|
} else {
|
||||||
|
return bValue - aValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const aStr = String(aValue).toLowerCase();
|
||||||
|
const bStr = String(bValue).toLowerCase();
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aStr.localeCompare(bStr);
|
||||||
|
} else {
|
||||||
|
return bStr.localeCompare(aStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSortIcon = (field: string) => {
|
||||||
|
if (sortField !== field) {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-64">
|
<div className="flex justify-center items-center h-64">
|
||||||
@@ -119,20 +207,50 @@ const ProductList: React.FC = () => {
|
|||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('name')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Name
|
Name
|
||||||
|
{getSortIcon('name')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('grocery')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Grocery
|
Grocery
|
||||||
|
{getSortIcon('grocery')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('category')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
Category
|
||||||
|
{getSortIcon('category')}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('brand')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Brand
|
Brand
|
||||||
|
{getSortIcon('brand')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('weight')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Weight
|
Weight
|
||||||
</th>
|
{getSortIcon('weight')}
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
</div>
|
||||||
Organic
|
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Actions
|
Actions
|
||||||
@@ -140,7 +258,7 @@ const ProductList: React.FC = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{products.map((product) => (
|
{sortedProducts.map((product) => (
|
||||||
<tr key={product.id} className="hover:bg-gray-50">
|
<tr key={product.id} className="hover:bg-gray-50">
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
@@ -149,7 +267,9 @@ const ProductList: React.FC = () => {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm text-gray-900">{product.grocery.name}</div>
|
<div className="text-sm text-gray-900">{product.grocery.name}</div>
|
||||||
<div className="text-xs text-gray-500">{product.grocery.category.name}</div>
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{product.grocery.category.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
{product.brand ? product.brand.name : '-'}
|
{product.brand ? product.brand.name : '-'}
|
||||||
@@ -157,15 +277,6 @@ const ProductList: React.FC = () => {
|
|||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
{product.weight ? `${product.weight}${product.weight_unit}` : '-'}
|
{product.weight ? `${product.weight}${product.weight_unit}` : '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
|
|
||||||
product.organic
|
|
||||||
? 'bg-green-100 text-green-800'
|
|
||||||
: 'bg-gray-100 text-gray-800'
|
|
||||||
}`}>
|
|
||||||
{product.organic ? 'Organic' : 'Conventional'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEdit(product)}
|
onClick={() => handleEdit(product)}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const ShopList: React.FC = () => {
|
|||||||
const [editingShop, setEditingShop] = useState<Shop | null>(null);
|
const [editingShop, setEditingShop] = useState<Shop | null>(null);
|
||||||
const [deletingShop, setDeletingShop] = useState<Shop | null>(null);
|
const [deletingShop, setDeletingShop] = useState<Shop | null>(null);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
const [sortField, setSortField] = useState<keyof Shop>('name');
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchShops();
|
fetchShops();
|
||||||
@@ -77,6 +79,58 @@ const ShopList: React.FC = () => {
|
|||||||
setDeletingShop(null);
|
setDeletingShop(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (field: keyof Shop) => {
|
||||||
|
if (field === sortField) {
|
||||||
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedShops = [...shops].sort((a, b) => {
|
||||||
|
let aValue = a[sortField];
|
||||||
|
let bValue = b[sortField];
|
||||||
|
|
||||||
|
// Handle null/undefined values
|
||||||
|
if (aValue === null || aValue === undefined) aValue = '';
|
||||||
|
if (bValue === null || bValue === undefined) bValue = '';
|
||||||
|
|
||||||
|
// Convert to string for comparison
|
||||||
|
const aStr = String(aValue).toLowerCase();
|
||||||
|
const bStr = String(bValue).toLowerCase();
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aStr.localeCompare(bStr);
|
||||||
|
} else {
|
||||||
|
return bStr.localeCompare(aStr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSortIcon = (field: keyof Shop) => {
|
||||||
|
if (sortField !== field) {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-64">
|
<div className="flex justify-center items-center h-64">
|
||||||
@@ -116,17 +170,41 @@ const ShopList: React.FC = () => {
|
|||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('name')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Name
|
Name
|
||||||
|
{getSortIcon('name')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('city')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
City
|
City
|
||||||
|
{getSortIcon('city')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('address')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Address
|
Address
|
||||||
|
{getSortIcon('address')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('created_at')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Created
|
Created
|
||||||
|
{getSortIcon('created_at')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Actions
|
Actions
|
||||||
@@ -134,7 +212,7 @@ const ShopList: React.FC = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{shops.map((shop) => (
|
{sortedShops.map((shop) => (
|
||||||
<tr key={shop.id} className="hover:bg-gray-50">
|
<tr key={shop.id} className="hover:bg-gray-50">
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const ShoppingEventList: React.FC = () => {
|
|||||||
const [hoveredEvent, setHoveredEvent] = useState<ShoppingEvent | null>(null);
|
const [hoveredEvent, setHoveredEvent] = useState<ShoppingEvent | null>(null);
|
||||||
const [showItemsPopup, setShowItemsPopup] = useState(false);
|
const [showItemsPopup, setShowItemsPopup] = useState(false);
|
||||||
const [popupPosition, setPopupPosition] = useState({ x: 0, y: 0 });
|
const [popupPosition, setPopupPosition] = useState({ x: 0, y: 0 });
|
||||||
|
const [sortField, setSortField] = useState<string>('date');
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchEvents();
|
fetchEvents();
|
||||||
@@ -157,6 +159,95 @@ const ShoppingEventList: React.FC = () => {
|
|||||||
setShowItemsPopup(true);
|
setShowItemsPopup(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (field: string) => {
|
||||||
|
if (field === sortField) {
|
||||||
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedEvents = [...events].sort((a, b) => {
|
||||||
|
let aValue: any;
|
||||||
|
let bValue: any;
|
||||||
|
|
||||||
|
switch (sortField) {
|
||||||
|
case 'shop':
|
||||||
|
aValue = a.shop.name;
|
||||||
|
bValue = b.shop.name;
|
||||||
|
break;
|
||||||
|
case 'date':
|
||||||
|
aValue = new Date(a.date);
|
||||||
|
bValue = new Date(b.date);
|
||||||
|
break;
|
||||||
|
case 'items':
|
||||||
|
aValue = a.products.length;
|
||||||
|
bValue = b.products.length;
|
||||||
|
break;
|
||||||
|
case 'total':
|
||||||
|
aValue = a.total_amount || 0;
|
||||||
|
bValue = b.total_amount || 0;
|
||||||
|
break;
|
||||||
|
case 'notes':
|
||||||
|
aValue = a.notes || '';
|
||||||
|
bValue = b.notes || '';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
aValue = '';
|
||||||
|
bValue = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle different data types
|
||||||
|
if (aValue instanceof Date && bValue instanceof Date) {
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aValue.getTime() - bValue.getTime();
|
||||||
|
} else {
|
||||||
|
return bValue.getTime() - aValue.getTime();
|
||||||
|
}
|
||||||
|
} else if (typeof aValue === 'number' && typeof bValue === 'number') {
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aValue - bValue;
|
||||||
|
} else {
|
||||||
|
return bValue - aValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// String comparison
|
||||||
|
const aStr = String(aValue).toLowerCase();
|
||||||
|
const bStr = String(bValue).toLowerCase();
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return aStr.localeCompare(bStr);
|
||||||
|
} else {
|
||||||
|
return bStr.localeCompare(aStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSortIcon = (field: string) => {
|
||||||
|
if (sortField !== field) {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortDirection === 'asc') {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4 ml-1 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-64">
|
<div className="flex justify-center items-center h-64">
|
||||||
@@ -196,20 +287,50 @@ const ShoppingEventList: React.FC = () => {
|
|||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('shop')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Shop
|
Shop
|
||||||
|
{getSortIcon('shop')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('date')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Date
|
Date
|
||||||
|
{getSortIcon('date')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('items')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Items
|
Items
|
||||||
|
{getSortIcon('items')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('total')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Total
|
Total
|
||||||
|
{getSortIcon('total')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 select-none"
|
||||||
|
onClick={() => handleSort('notes')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
Notes
|
Notes
|
||||||
|
{getSortIcon('notes')}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Actions
|
Actions
|
||||||
@@ -217,7 +338,7 @@ const ShoppingEventList: React.FC = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{events.map((event) => (
|
{sortedEvents.map((event) => (
|
||||||
<tr key={event.id} className="hover:bg-gray-50">
|
<tr key={event.id} className="hover:bg-gray-50">
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm font-medium text-gray-900">{event.shop.name}</div>
|
<div className="text-sm font-medium text-gray-900">{event.shop.name}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user