bug list fixes!

This commit is contained in:
amee
2026-04-09 11:26:50 +05:30
parent 82082cb862
commit 6075f1ad10
10 changed files with 139 additions and 60 deletions
@@ -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) => {
+2 -2
View File
@@ -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')}
+2 -1
View File
@@ -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,
}; };
+6
View File
@@ -63,5 +63,11 @@
"sectionTitle": "تكوين الألوان", "sectionTitle": "تكوين الألوان",
"required": "مطلوب" "required": "مطلوب"
} }
},
"deleteModal": {
"title": "حذف النظام",
"message": "هل أنت متأكد أنك تريد حذف هذا النظام؟",
"confirm": "حذف",
"cancel": "إلغاء"
} }
} }
+6
View File
@@ -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"
} }
} }