bug list fixes!
This commit is contained in:
@@ -4,6 +4,7 @@ import { ArrowLeft, Plus, Edit, Trash2, Check, X } from 'lucide-react';
|
|||||||
import { adminModuleApi } from '../AdminModuleApi';
|
import { adminModuleApi } from '../AdminModuleApi';
|
||||||
import type { ModuleEnvironment, Module } from '../AdminModuleTypes';
|
import type { ModuleEnvironment, Module } from '../AdminModuleTypes';
|
||||||
import EnvironmentForm from './EnvironmentForm';
|
import EnvironmentForm from './EnvironmentForm';
|
||||||
|
import { CustomConfirmationModal } from '../../../../components/custom';
|
||||||
|
|
||||||
const ModuleEnvironments = () => {
|
const ModuleEnvironments = () => {
|
||||||
const { moduleId } = useParams<{ moduleId: string }>();
|
const { moduleId } = useParams<{ moduleId: string }>();
|
||||||
@@ -13,6 +14,7 @@ const ModuleEnvironments = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [selectedEnv, setSelectedEnv] = useState<ModuleEnvironment | null>(null);
|
const [selectedEnv, setSelectedEnv] = useState<ModuleEnvironment | null>(null);
|
||||||
|
const [envToDelete, setEnvToDelete] = useState<ModuleEnvironment | null>(null);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!moduleId) return;
|
if (!moduleId) return;
|
||||||
@@ -47,14 +49,17 @@ const ModuleEnvironments = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (env: ModuleEnvironment) => {
|
const handleDelete = async (env: ModuleEnvironment) => {
|
||||||
if (!moduleId) return;
|
setEnvToDelete(env);
|
||||||
if (!confirm(`Delete environment "${env.slug}"?`)) return;
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!moduleId || !envToDelete) return;
|
||||||
try {
|
try {
|
||||||
await adminModuleApi.deleteEnvironment(moduleId, env.id);
|
await adminModuleApi.deleteEnvironment(moduleId, envToDelete.id);
|
||||||
|
setEnvToDelete(null);
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
alert(error.response?.data?.detail || 'Failed to delete environment');
|
console.error('Failed to delete environment', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,6 +173,15 @@ const ModuleEnvironments = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<CustomConfirmationModal
|
||||||
|
isOpen={!!envToDelete}
|
||||||
|
onClose={() => setEnvToDelete(null)}
|
||||||
|
onConfirm={confirmDelete}
|
||||||
|
title="Delete Environment"
|
||||||
|
description={`Are you sure you want to delete ${envToDelete?.slug} environment?`}
|
||||||
|
variant="danger"
|
||||||
|
/>
|
||||||
|
|
||||||
{showForm && moduleId && (
|
{showForm && moduleId && (
|
||||||
<EnvironmentForm
|
<EnvironmentForm
|
||||||
moduleId={moduleId}
|
moduleId={moduleId}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Plus, Edit, Trash2, Settings, Shield, Eye, EyeOff } from 'lucide-react';
|
import { Plus, Edit, Trash2, Settings, Shield, Eye, EyeOff } from 'lucide-react';
|
||||||
import { useModuleApi } from '../hooks/useModuleApi';
|
import { useModuleApi } from '../hooks/useModuleApi';
|
||||||
import type { Module } from '../AdminModuleTypes';
|
import type { Module } from '../AdminModuleTypes';
|
||||||
|
import { adminModuleApi } from '../AdminModuleApi';
|
||||||
import ModuleForm from './ModuleForm';
|
import ModuleForm from './ModuleForm';
|
||||||
import CustomConfirmationModal from '../../../../components/custom/CustomConfirmationModal';
|
import CustomConfirmationModal from '../../../../components/custom/CustomConfirmationModal';
|
||||||
import CustomButton from '../../../../components/custom/CustomButton';
|
import CustomButton from '../../../../components/custom/CustomButton';
|
||||||
@@ -11,11 +12,12 @@ import CustomButton from '../../../../components/custom/CustomButton';
|
|||||||
const ModuleList = () => {
|
const ModuleList = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation(['modules', 'common']);
|
const { t } = useTranslation(['modules', 'common']);
|
||||||
const { listModules, deleteModule, loading } = useModuleApi();
|
const { listModules, deleteModule, updateModule, loading } = useModuleApi();
|
||||||
const [modules, setModules] = useState<Module[]>([]);
|
const [modules, setModules] = useState<Module[]>([]);
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [selectedModule, setSelectedModule] = useState<Module | null>(null);
|
const [selectedModule, setSelectedModule] = useState<Module | null>(null);
|
||||||
const [moduleToDelete, setModuleToDelete] = useState<Module | null>(null);
|
const [moduleToDelete, setModuleToDelete] = useState<Module | null>(null);
|
||||||
|
const [togglingId, setTogglingId] = useState<string | null>(null);
|
||||||
|
|
||||||
const fetchModules = async () => {
|
const fetchModules = async () => {
|
||||||
const data = await listModules();
|
const data = await listModules();
|
||||||
@@ -44,7 +46,6 @@ const ModuleList = () => {
|
|||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!moduleToDelete) return;
|
if (!moduleToDelete) return;
|
||||||
|
|
||||||
const success = await deleteModule(moduleToDelete.id);
|
const success = await deleteModule(moduleToDelete.id);
|
||||||
if (success) {
|
if (success) {
|
||||||
fetchModules();
|
fetchModules();
|
||||||
@@ -106,11 +107,36 @@ const ModuleList = () => {
|
|||||||
<p className="text-sm text-(--text-secondary)">{module.module_id}</p>
|
<p className="text-sm text-(--text-secondary)">{module.module_id}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div
|
||||||
|
className={`flex items-center gap-1 transition-opacity ${
|
||||||
|
togglingId === module.id
|
||||||
|
? 'opacity-50 cursor-not-allowed'
|
||||||
|
: 'cursor-pointer hover:text-blue-600'
|
||||||
|
}`}
|
||||||
|
title={module.status === 'active' ? 'Deactivate' : 'Activate'}
|
||||||
|
onClick={async () => {
|
||||||
|
if (togglingId) return;
|
||||||
|
try {
|
||||||
|
setTogglingId(module.id);
|
||||||
|
const updated = await adminModuleApi.updateModule(module.id, {
|
||||||
|
status: module.status === 'active' ? 'inactive' : 'active'
|
||||||
|
});
|
||||||
|
if (updated) {
|
||||||
|
setModules(prev => prev.map(m =>
|
||||||
|
m.id === module.id ? { ...m, status: updated.status } : m
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Toggle failed:", error);
|
||||||
|
} finally {
|
||||||
|
setTogglingId(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{module.status === 'active' ? (
|
{module.status === 'active' ? (
|
||||||
<Eye className="w-5 h-5 text-green-600" />
|
<Eye className="w-5 h-5 text-green-600" />
|
||||||
) : (
|
) : (
|
||||||
<EyeOff className="w-5 h-5 text-gray-400" />
|
<EyeOff className="w-5 h-5 text-gray-600" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const useModuleApi = (): UseModuleApiResult => {
|
|||||||
[]);
|
[]);
|
||||||
|
|
||||||
const updateModule = useCallback((id: string, data: ModuleUpdate) =>
|
const updateModule = useCallback((id: string, data: ModuleUpdate) =>
|
||||||
wrapRequest(() => adminModuleApi.updateModule(id, data), 'update module', 'Module updated successfully'),
|
wrapRequest(() => adminModuleApi.updateModule(id, data), 'update module'),
|
||||||
[]);
|
[]);
|
||||||
|
|
||||||
const deleteModule = useCallback(async (id: string) => {
|
const deleteModule = useCallback(async (id: string) => {
|
||||||
|
|||||||
@@ -129,8 +129,8 @@ const ProfilePage: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
if (passwordForm.newPassword === passwordForm.confirmPassword) {
|
||||||
setPasswordError(t('messages.passwordMismatch'));
|
setPasswordError("New password must be different from your current password");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ const AllRoles = () => {
|
|||||||
footer={
|
footer={
|
||||||
<>
|
<>
|
||||||
<CustomButton variant="outlined" onClick={closeEdit} disabled={isSaving}>{t('actions.cancel')}</CustomButton>
|
<CustomButton variant="outlined" onClick={closeEdit} disabled={isSaving}>{t('actions.cancel')}</CustomButton>
|
||||||
<CustomButton type="submit" form="edit-role-form" variant="primary" loading={isSaving}>{t('update')}</CustomButton>
|
<CustomButton type="submit" form="edit-role-form" variant="primary" loading={isSaving}>{t('actions.update')}</CustomButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { useEffect, useState, useMemo } from "react";
|
import React, { useEffect, useState, useMemo } from "react";
|
||||||
import { Plus, Edit2, Trash2 } from "lucide-react";
|
import { Plus, Edit2, Trash2 } from "lucide-react";
|
||||||
import { toast } from "react-toastify";
|
import { CustomButton } from "../../../components/custom";
|
||||||
import {CustomButton} from "../../../components/custom";
|
|
||||||
import DataTable, { type ColumnDef } from "../../../components/custom/CustomTable";
|
import DataTable, { type ColumnDef } from "../../../components/custom/CustomTable";
|
||||||
import { CustomActionMenu, CustomActionItem, CustomStatus } from "../../../components/custom";
|
import { CustomActionMenu, CustomActionItem, CustomStatus, CustomConfirmationModal } from "../../../components/custom";
|
||||||
import { Loader } from "../../../components/custom/CustomLoader";
|
import { Loader } from "../../../components/custom/CustomLoader";
|
||||||
import { paletteApi } from "../PaletteApi";
|
import { paletteApi } from "../PaletteApi";
|
||||||
import type { ColorPalette } from "../ThemeTypes";
|
import type { ColorPalette } from "../ThemeTypes";
|
||||||
@@ -18,6 +17,8 @@ const AllPalettes: React.FC = () => {
|
|||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [editingPalette, setEditingPalette] = useState<ColorPalette | null>(null);
|
const [editingPalette, setEditingPalette] = useState<ColorPalette | null>(null);
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [deleteId, setDeleteId] = useState<string | null>(null);
|
||||||
const { refreshTheme } = useTheme();
|
const { refreshTheme } = useTheme();
|
||||||
|
|
||||||
const fetchPalettes = async () => {
|
const fetchPalettes = async () => {
|
||||||
@@ -49,14 +50,22 @@ const AllPalettes: React.FC = () => {
|
|||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = (id: string, isDefault: boolean) => {
|
||||||
if (!window.confirm(t('theme:confirmDelete'))) return;
|
if (isDefault) return;
|
||||||
|
setDeleteId(id);
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDelete = async () => {
|
||||||
|
if (!deleteId) return;
|
||||||
try {
|
try {
|
||||||
await paletteApi.deletePalette(id);
|
await paletteApi.deletePalette(deleteId);
|
||||||
toast.success(t('theme:success.deleted'));
|
|
||||||
fetchPalettes();
|
fetchPalettes();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// toast handled by api client
|
console.error("Failed to delete palette", error);
|
||||||
|
} finally {
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setDeleteId(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,10 +74,8 @@ const AllPalettes: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
if (editingPalette) {
|
if (editingPalette) {
|
||||||
await paletteApi.updatePalette(editingPalette.id, data);
|
await paletteApi.updatePalette(editingPalette.id, data);
|
||||||
toast.success(t('theme:success.updated'));
|
|
||||||
} else {
|
} else {
|
||||||
await paletteApi.createPalette(data);
|
await paletteApi.createPalette(data);
|
||||||
toast.success(t('theme:success.created'));
|
|
||||||
}
|
}
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
fetchPalettes();
|
fetchPalettes();
|
||||||
@@ -136,7 +143,7 @@ const AllPalettes: React.FC = () => {
|
|||||||
</CustomActionItem>
|
</CustomActionItem>
|
||||||
|
|
||||||
<CustomActionItem
|
<CustomActionItem
|
||||||
onClick={() => handleDelete(row.id)}
|
onClick={() => handleDelete(row.id, row.is_default)}
|
||||||
className={`${row.is_default ? "opacity-50 cursor-not-allowed" : "text-red-600 hover:text-red-700 hover:bg-red-50"}`}
|
className={`${row.is_default ? "opacity-50 cursor-not-allowed" : "text-red-600 hover:text-red-700 hover:bg-red-50"}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -155,10 +162,11 @@ const AllPalettes: React.FC = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[]
|
[t]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -186,6 +194,7 @@ const AllPalettes: React.FC = () => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
enableSearchDropdown={true}
|
enableSearchDropdown={true}
|
||||||
search="name"
|
search="name"
|
||||||
|
buildSuggestionLabel={(row) => row.name}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -197,6 +206,18 @@ const AllPalettes: React.FC = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CustomConfirmationModal
|
||||||
|
isOpen={isDeleteModalOpen}
|
||||||
|
onConfirm={handleConfirmDelete}
|
||||||
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
|
title={t('theme:deleteModal.title')}
|
||||||
|
description={t('theme:deleteModal.message')}
|
||||||
|
confirmText={t('theme:deleteModal.confirm')}
|
||||||
|
cancelText={t('theme:deleteModal.cancel')}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
CustomActionMenu,
|
CustomActionMenu,
|
||||||
CustomActionItem,
|
CustomActionItem,
|
||||||
CustomStatus,
|
CustomStatus,
|
||||||
|
CustomPhoneInput,
|
||||||
} from "../../../components/custom";
|
} from "../../../components/custom";
|
||||||
import type { ColumnDef } from "../../../components/custom/CustomTable";
|
import type { ColumnDef } from "../../../components/custom/CustomTable";
|
||||||
import type {
|
import type {
|
||||||
@@ -281,6 +282,10 @@ const AllUsers = () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleEditPhoneChange = useCallback((val: string | undefined) => {
|
||||||
|
setEditForm((prev) => ({ ...prev, phone_number: val || "" }));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleStatusChange = useCallback(
|
const handleStatusChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setEditForm((prev) => ({
|
setEditForm((prev) => ({
|
||||||
@@ -523,7 +528,7 @@ const AllUsers = () => {
|
|||||||
<CustomInput label={t('fields.firstName')} name="first_name" value={editForm.first_name} onChange={handleEditChange} required />
|
<CustomInput label={t('fields.firstName')} name="first_name" value={editForm.first_name} onChange={handleEditChange} required />
|
||||||
<CustomInput label={t('fields.lastName')} name="last_name" value={editForm.last_name} onChange={handleEditChange} />
|
<CustomInput label={t('fields.lastName')} name="last_name" value={editForm.last_name} onChange={handleEditChange} />
|
||||||
<CustomInput label={t('fields.email')} name="email" type="email" value={editForm.email} onChange={handleEditChange} required />
|
<CustomInput label={t('fields.email')} name="email" type="email" value={editForm.email} onChange={handleEditChange} required />
|
||||||
<CustomInput label={t('fields.phone')} name="phone_number" type="tel" maxLength={10} phonePrefix="+91" value={editForm.phone_number} onChange={handleEditChange} />
|
<CustomPhoneInput label={t('fields.phone')} name="phone_number" value={editForm.phone_number} onChange={handleEditPhoneChange} defaultCountry="IN" />
|
||||||
<CustomInput label={t('fields.tenant')} value={getTenantName(currentUser?.tenant_id ?? editForm.tenant_id) || "--"} disabled readOnly />
|
<CustomInput label={t('fields.tenant')} value={getTenantName(currentUser?.tenant_id ?? editForm.tenant_id) || "--"} disabled readOnly />
|
||||||
<CustomDropdown
|
<CustomDropdown
|
||||||
label={t('fields.role')}
|
label={t('fields.role')}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import CustomIncrement from "./CustomIncrement";
|
|||||||
import CustomLoader from "./CustomLoader";
|
import CustomLoader from "./CustomLoader";
|
||||||
import CustomActionMenu, { CustomActionItem } from "./CustomActionMenu";
|
import CustomActionMenu, { CustomActionItem } from "./CustomActionMenu";
|
||||||
import CustomStatus from "./CustomStatus";
|
import CustomStatus from "./CustomStatus";
|
||||||
|
import { CustomPhoneInput } from "./CustomPhoneInput";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CustomInput,
|
CustomInput,
|
||||||
@@ -44,5 +45,5 @@ export {
|
|||||||
CustomActionMenu,
|
CustomActionMenu,
|
||||||
CustomActionItem,
|
CustomActionItem,
|
||||||
CustomStatus,
|
CustomStatus,
|
||||||
|
CustomPhoneInput,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,5 +63,11 @@
|
|||||||
"sectionTitle": "تكوين الألوان",
|
"sectionTitle": "تكوين الألوان",
|
||||||
"required": "مطلوب"
|
"required": "مطلوب"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"deleteModal": {
|
||||||
|
"title": "حذف النظام",
|
||||||
|
"message": "هل أنت متأكد أنك تريد حذف هذا النظام؟",
|
||||||
|
"confirm": "حذف",
|
||||||
|
"cancel": "إلغاء"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,5 +63,11 @@
|
|||||||
"sectionTitle": "Color Configuration",
|
"sectionTitle": "Color Configuration",
|
||||||
"required": "Required"
|
"required": "Required"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"deleteModal": {
|
||||||
|
"title": "Delete Palette",
|
||||||
|
"message": "Are you sure you want to delete this palette?",
|
||||||
|
"confirm": "Delete",
|
||||||
|
"cancel": "Cancel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user