feat: implement useOverlayDismiss hook for consistent modal dismissal behavior
This commit is contained in:
@@ -27,6 +27,7 @@ import ClinicDoctorsManager from '../components/ClinicDoctorsManager';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import { latinDigitsField } from '../lib/forms';
|
||||
import Switch from '../components/ui/Switch';
|
||||
import { useOverlayDismiss } from '../hooks/useOverlayDismiss';
|
||||
|
||||
// Fix leaflet icons
|
||||
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
||||
@@ -238,9 +239,11 @@ function EditModal({ clinic, onClose, onSaved }: {
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'basic' | 'tags' | 'social'>('basic');
|
||||
|
||||
const dismiss = useOverlayDismiss(onClose);
|
||||
|
||||
return createPortal(
|
||||
<div className="overlay" onClick={onClose}>
|
||||
<div className="modal" style={{ maxWidth: 560, maxHeight: '90vh', overflowY: 'auto' }} onClick={e => e.stopPropagation()}>
|
||||
<div className="overlay" {...dismiss}>
|
||||
<div className="modal" style={{ maxWidth: 560, maxHeight: '90vh', overflowY: 'auto' }}>
|
||||
<div className="modal-head">
|
||||
<b>ویرایش کلینیک</b>
|
||||
<button className="mini-btn" onClick={onClose}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router';
|
||||
import {
|
||||
@@ -26,6 +26,7 @@ import ConfirmDialog from '../components/ui/ConfirmDialog';
|
||||
import ChangeLoginMobileModal from '../components/ChangeLoginMobileModal';
|
||||
import { latinDigitsField } from '../lib/forms';
|
||||
import TourButton from '../components/ui/TourButton';
|
||||
import { useOverlayDismiss } from '../hooks/useOverlayDismiss';
|
||||
|
||||
const HUES_LIST = [256, 205, 162, 295, 272];
|
||||
|
||||
@@ -43,6 +44,8 @@ export default function ClinicsPage() {
|
||||
const isRepresentation = primaryRole === 'representation';
|
||||
// وضعیت لیست در URL میماند تا «بازگشت» از صفحهٔ جزئیات، همین فیلترها و صفحه را برگرداند.
|
||||
const [urlState, setUrlState] = useUrlState({ page: '1', search: '', status: '' });
|
||||
const closeAdd = useCallback(() => setAddOpen(false), []);
|
||||
const addDismiss = useOverlayDismiss(closeAdd);
|
||||
const page = pageOf(urlState.page);
|
||||
const search = urlState.search;
|
||||
const statusFilter = urlState.status;
|
||||
@@ -264,8 +267,8 @@ export default function ClinicsPage() {
|
||||
{/* Add Modal */}
|
||||
{addOpen && (
|
||||
<Portal>
|
||||
<div className="overlay" onClick={() => setAddOpen(false)}>
|
||||
<div className="modal" style={{ maxWidth: 420 }} onClick={(e) => e.stopPropagation()}>
|
||||
<div className="overlay" {...addDismiss}>
|
||||
<div className="modal" style={{ maxWidth: 420 }}>
|
||||
<div className="modal-head">
|
||||
<b>افزودن کلینیک</b>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { CheckIcon, XMarkIcon, PhoneIcon } from '@heroicons/react/24/outline';
|
||||
import { toast } from 'sonner';
|
||||
@@ -9,6 +9,7 @@ import { formatDate } from '../lib/utils';
|
||||
import PageHeader from '../components/ui/PageHeader';
|
||||
import Pagination from '../components/ui/Pagination';
|
||||
import ConfirmDialog from '../components/ui/ConfirmDialog';
|
||||
import { useOverlayDismiss } from '../hooks/useOverlayDismiss';
|
||||
|
||||
interface PreRegistration {
|
||||
uuid: string;
|
||||
@@ -49,6 +50,8 @@ export default function PreRegistrationsPage() {
|
||||
const [approveTarget, setApproveTarget] = useState<PreRegistration | null>(null);
|
||||
const [rejectTarget, setRejectTarget] = useState<PreRegistration | null>(null);
|
||||
const [rejectNote, setRejectNote] = useState('');
|
||||
const closeReject = useCallback(() => setRejectTarget(null), []);
|
||||
const rejectDismiss = useOverlayDismiss(closeReject);
|
||||
const limit = 20;
|
||||
|
||||
const { data, isLoading } = useQuery<PaginatedResponse<PreRegistration>>({
|
||||
@@ -206,8 +209,8 @@ export default function PreRegistrationsPage() {
|
||||
{/* Reject Dialog */}
|
||||
{rejectTarget && (
|
||||
<Portal>
|
||||
<div className="overlay" onClick={() => setRejectTarget(null)}>
|
||||
<div className="modal" style={{ maxWidth: 420 }} onClick={(e) => e.stopPropagation()}>
|
||||
<div className="overlay" {...rejectDismiss}>
|
||||
<div className="modal" style={{ maxWidth: 420 }}>
|
||||
<div className="modal-head">
|
||||
<b>رد درخواست — {rejectTarget.name}</b>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ import ConfirmDialog from '../components/ui/ConfirmDialog';
|
||||
import Pagination from '../components/ui/Pagination';
|
||||
import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { useOverlayDismiss } from '../hooks/useOverlayDismiss';
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -108,10 +109,12 @@ function ChangeRoleModal({ user, onClose, onSave, loading }: {
|
||||
onSave: (role: string) => void; loading: boolean;
|
||||
}) {
|
||||
const [selected, setSelected] = useState(getPrimaryRole(user.roles));
|
||||
const dismiss = useOverlayDismiss(onClose);
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<div className="overlay" onClick={onClose}>
|
||||
<div className="modal" style={{ maxWidth: 400 }} onClick={(e) => e.stopPropagation()}>
|
||||
<div className="overlay" {...dismiss}>
|
||||
<div className="modal" style={{ maxWidth: 400 }}>
|
||||
<div className="modal-head">
|
||||
<h2 style={{ fontSize: 16 }}>تغییر نقش</h2>
|
||||
<button className="mini-btn" onClick={onClose}>
|
||||
|
||||
Reference in New Issue
Block a user