Fixed issue scrolling on mobile
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { brandApi } from '../services/api';
|
import { brandApi } from '../services/api';
|
||||||
import { Brand } from '../types';
|
import { Brand } from '../types';
|
||||||
|
import { useBodyScrollLock } from '../hooks/useBodyScrollLock';
|
||||||
|
|
||||||
interface AddBrandModalProps {
|
interface AddBrandModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -22,6 +23,9 @@ const AddBrandModal: React.FC<AddBrandModalProps> = ({ isOpen, onClose, onBrandA
|
|||||||
|
|
||||||
const isEditMode = !!editBrand;
|
const isEditMode = !!editBrand;
|
||||||
|
|
||||||
|
// Use body scroll lock when modal is open
|
||||||
|
useBodyScrollLock(isOpen);
|
||||||
|
|
||||||
// Initialize form data when editing
|
// Initialize form data when editing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editBrand) {
|
if (editBrand) {
|
||||||
@@ -103,8 +107,19 @@ const AddBrandModal: React.FC<AddBrandModalProps> = ({ isOpen, onClose, onBrandA
|
|||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div
|
||||||
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close modal if clicking on backdrop
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { GroceryCategory, GroceryCategoryCreate } from '../types';
|
import { GroceryCategory, GroceryCategoryCreate } from '../types';
|
||||||
import { groceryCategoryApi } from '../services/api';
|
import { groceryCategoryApi } from '../services/api';
|
||||||
|
import { useBodyScrollLock } from '../hooks/useBodyScrollLock';
|
||||||
|
|
||||||
interface AddGroceryCategoryModalProps {
|
interface AddGroceryCategoryModalProps {
|
||||||
category?: GroceryCategory | null;
|
category?: GroceryCategory | null;
|
||||||
@@ -8,6 +9,9 @@ interface AddGroceryCategoryModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AddGroceryCategoryModal: React.FC<AddGroceryCategoryModalProps> = ({ category, onClose }) => {
|
const AddGroceryCategoryModal: React.FC<AddGroceryCategoryModalProps> = ({ category, onClose }) => {
|
||||||
|
// Use body scroll lock when modal is open (always open when component is rendered)
|
||||||
|
useBodyScrollLock(true);
|
||||||
|
|
||||||
const [formData, setFormData] = useState<GroceryCategoryCreate>({
|
const [formData, setFormData] = useState<GroceryCategoryCreate>({
|
||||||
name: ''
|
name: ''
|
||||||
});
|
});
|
||||||
@@ -69,8 +73,19 @@ const AddGroceryCategoryModal: React.FC<AddGroceryCategoryModalProps> = ({ categ
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div
|
||||||
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close modal if clicking on backdrop
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
||||||
{isEditMode ? 'Edit Category' : 'Add New Category'}
|
{isEditMode ? 'Edit Category' : 'Add New Category'}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { productApi, brandApi, groceryCategoryApi } from '../services/api';
|
import { productApi, brandApi, groceryCategoryApi } from '../services/api';
|
||||||
import { Product, Brand, GroceryCategory } from '../types';
|
import { Product, Brand, GroceryCategory } from '../types';
|
||||||
|
import { useBodyScrollLock } from '../hooks/useBodyScrollLock';
|
||||||
|
|
||||||
interface AddProductModalProps {
|
interface AddProductModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -34,6 +35,9 @@ const AddProductModal: React.FC<AddProductModalProps> = ({ isOpen, onClose, onPr
|
|||||||
|
|
||||||
const weightUnits = ['piece', 'g', 'kg', 'lb', 'oz', 'ml', 'l'];
|
const weightUnits = ['piece', 'g', 'kg', 'lb', 'oz', 'ml', 'l'];
|
||||||
|
|
||||||
|
// Use body scroll lock when modal is open
|
||||||
|
useBodyScrollLock(isOpen);
|
||||||
|
|
||||||
// Fetch brands and categories when modal opens
|
// Fetch brands and categories when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
@@ -167,8 +171,19 @@ const AddProductModal: React.FC<AddProductModalProps> = ({ isOpen, onClose, onPr
|
|||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div
|
||||||
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close modal if clicking on backdrop
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { shopApi, brandApi, brandInShopApi } from '../services/api';
|
import { shopApi, brandApi, brandInShopApi } from '../services/api';
|
||||||
import { Shop, Brand, BrandInShop } from '../types';
|
import { Shop, Brand, BrandInShop } from '../types';
|
||||||
|
import { useBodyScrollLock } from '../hooks/useBodyScrollLock';
|
||||||
|
|
||||||
interface AddShopModalProps {
|
interface AddShopModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -29,6 +30,9 @@ const AddShopModal: React.FC<AddShopModalProps> = ({ isOpen, onClose, onShopAdde
|
|||||||
|
|
||||||
const isEditMode = !!editShop;
|
const isEditMode = !!editShop;
|
||||||
|
|
||||||
|
// Use body scroll lock when modal is open
|
||||||
|
useBodyScrollLock(isOpen);
|
||||||
|
|
||||||
// Load brands when modal opens
|
// Load brands when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
@@ -198,8 +202,19 @@ const AddShopModal: React.FC<AddShopModalProps> = ({ isOpen, onClose, onShopAdde
|
|||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div
|
||||||
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white max-h-[80vh] overflow-y-auto">
|
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close modal if clicking on backdrop
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white max-h-[80vh] overflow-y-auto"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { Shop, Product, ShoppingEventCreate, ProductInEvent, ShoppingEvent, BrandInShop } from '../types';
|
import { Shop, Product, ShoppingEventCreate, ProductInEvent, ShoppingEvent, BrandInShop } from '../types';
|
||||||
import { shopApi, productApi, shoppingEventApi, brandInShopApi } from '../services/api';
|
import { shopApi, productApi, shoppingEventApi, brandInShopApi } from '../services/api';
|
||||||
|
import { useBodyScrollLock } from '../hooks/useBodyScrollLock';
|
||||||
|
|
||||||
interface AddShoppingEventModalProps {
|
interface AddShoppingEventModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -15,6 +16,9 @@ const AddShoppingEventModal: React.FC<AddShoppingEventModalProps> = ({
|
|||||||
onEventAdded,
|
onEventAdded,
|
||||||
editEvent
|
editEvent
|
||||||
}) => {
|
}) => {
|
||||||
|
// Use body scroll lock when modal is open
|
||||||
|
useBodyScrollLock(isOpen);
|
||||||
|
|
||||||
const [shops, setShops] = useState<Shop[]>([]);
|
const [shops, setShops] = useState<Shop[]>([]);
|
||||||
const [products, setProducts] = useState<Product[]>([]);
|
const [products, setProducts] = useState<Product[]>([]);
|
||||||
const [shopBrands, setShopBrands] = useState<BrandInShop[]>([]);
|
const [shopBrands, setShopBrands] = useState<BrandInShop[]>([]);
|
||||||
@@ -273,8 +277,19 @@ const AddShoppingEventModal: React.FC<AddShoppingEventModalProps> = ({
|
|||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div
|
||||||
<div className="relative min-h-screen md:min-h-0 md:top-10 mx-auto p-4 md:p-5 w-full md:max-w-4xl md:shadow-lg md:rounded-md bg-white">
|
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close modal if clicking on backdrop
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative min-h-screen md:min-h-0 md:top-10 mx-auto p-4 md:p-5 w-full md:max-w-4xl md:shadow-lg md:rounded-md bg-white"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-lg md:text-xl font-medium text-gray-900">
|
<h3 className="text-lg md:text-xl font-medium text-gray-900">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { useBodyScrollLock } from '../hooks/useBodyScrollLock';
|
||||||
|
|
||||||
interface ConfirmDeleteModalProps {
|
interface ConfirmDeleteModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -17,6 +18,9 @@ const ConfirmDeleteModal: React.FC<ConfirmDeleteModalProps> = ({
|
|||||||
message,
|
message,
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}) => {
|
}) => {
|
||||||
|
// Use body scroll lock when modal is open
|
||||||
|
useBodyScrollLock(isOpen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
|
|
||||||
@@ -38,8 +42,19 @@ const ConfirmDeleteModal: React.FC<ConfirmDeleteModalProps> = ({
|
|||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div
|
||||||
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close modal if clicking on backdrop
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import Papa from 'papaparse';
|
import Papa from 'papaparse';
|
||||||
import { Brand, GroceryCategory, Product } from '../types';
|
import { Brand, GroceryCategory, Product } from '../types';
|
||||||
import { brandApi, groceryCategoryApi, productApi } from '../services/api';
|
import { brandApi, groceryCategoryApi, productApi } from '../services/api';
|
||||||
|
import { useBodyScrollLock } from '../hooks/useBodyScrollLock';
|
||||||
|
|
||||||
interface ImportExportModalProps {
|
interface ImportExportModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -18,6 +19,9 @@ interface ImportResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ImportExportModal: React.FC<ImportExportModalProps> = ({ isOpen, onClose, onDataChanged }) => {
|
const ImportExportModal: React.FC<ImportExportModalProps> = ({ isOpen, onClose, onDataChanged }) => {
|
||||||
|
// Use body scroll lock when modal is open
|
||||||
|
useBodyScrollLock(isOpen);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<'export' | 'import'>('export');
|
const [activeTab, setActiveTab] = useState<'export' | 'import'>('export');
|
||||||
const [selectedEntity, setSelectedEntity] = useState<EntityType>('brands');
|
const [selectedEntity, setSelectedEntity] = useState<EntityType>('brands');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -338,8 +342,19 @@ const ImportExportModal: React.FC<ImportExportModalProps> = ({ isOpen, onClose,
|
|||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<div
|
||||||
<div className="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white max-h-[90vh] overflow-y-auto">
|
className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close modal if clicking on backdrop
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white max-h-[90vh] overflow-y-auto"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">
|
||||||
|
|||||||
33
frontend/src/hooks/useBodyScrollLock.ts
Normal file
33
frontend/src/hooks/useBodyScrollLock.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export const useBodyScrollLock = (isLocked: boolean) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLocked) return;
|
||||||
|
|
||||||
|
// Store original body overflow and position
|
||||||
|
const originalOverflow = document.body.style.overflow;
|
||||||
|
const originalPosition = document.body.style.position;
|
||||||
|
const originalTop = document.body.style.top;
|
||||||
|
const originalWidth = document.body.style.width;
|
||||||
|
|
||||||
|
// Get current scroll position
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
|
||||||
|
// Lock the body scroll
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
document.body.style.position = 'fixed';
|
||||||
|
document.body.style.top = `-${scrollY}px`;
|
||||||
|
document.body.style.width = '100%';
|
||||||
|
|
||||||
|
// Cleanup function to restore original styles
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = originalOverflow;
|
||||||
|
document.body.style.position = originalPosition;
|
||||||
|
document.body.style.top = originalTop;
|
||||||
|
document.body.style.width = originalWidth;
|
||||||
|
|
||||||
|
// Restore scroll position
|
||||||
|
window.scrollTo(0, scrollY);
|
||||||
|
};
|
||||||
|
}, [isLocked]);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user