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 type { ModuleEnvironment, Module } from '../AdminModuleTypes';
import EnvironmentForm from './EnvironmentForm';
import { CustomConfirmationModal } from '../../../../components/custom';
const ModuleEnvironments = () => {
const { moduleId } = useParams<{ moduleId: string }>();
@@ -13,6 +14,7 @@ const ModuleEnvironments = () => {
const [loading, setLoading] = useState(true);
const [showForm, setShowForm] = useState(false);
const [selectedEnv, setSelectedEnv] = useState<ModuleEnvironment | null>(null);
const [envToDelete, setEnvToDelete] = useState<ModuleEnvironment | null>(null);
const fetchData = async () => {
if (!moduleId) return;
@@ -47,14 +49,17 @@ const ModuleEnvironments = () => {
};
const handleDelete = async (env: ModuleEnvironment) => {
if (!moduleId) return;
if (!confirm(`Delete environment "${env.slug}"?`)) return;
setEnvToDelete(env);
};
const confirmDelete = async () => {
if (!moduleId || !envToDelete) return;
try {
await adminModuleApi.deleteEnvironment(moduleId, env.id);
await adminModuleApi.deleteEnvironment(moduleId, envToDelete.id);
setEnvToDelete(null);
fetchData();
} 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>
)}
<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 && (
<EnvironmentForm
moduleId={moduleId}
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Plus, Edit, Trash2, Settings, Shield, Eye, EyeOff } from 'lucide-react';
import { useModuleApi } from '../hooks/useModuleApi';
import type { Module } from '../AdminModuleTypes';
import { adminModuleApi } from '../AdminModuleApi';
import ModuleForm from './ModuleForm';
import CustomConfirmationModal from '../../../../components/custom/CustomConfirmationModal';
import CustomButton from '../../../../components/custom/CustomButton';
@@ -11,11 +12,12 @@ import CustomButton from '../../../../components/custom/CustomButton';
const ModuleList = () => {
const navigate = useNavigate();
const { t } = useTranslation(['modules', 'common']);
const { listModules, deleteModule, loading } = useModuleApi();
const { listModules, deleteModule, updateModule, loading } = useModuleApi();
const [modules, setModules] = useState<Module[]>([]);
const [showForm, setShowForm] = useState(false);
const [selectedModule, setSelectedModule] = useState<Module | null>(null);
const [moduleToDelete, setModuleToDelete] = useState<Module | null>(null);
const [togglingId, setTogglingId] = useState<string | null>(null);
const fetchModules = async () => {
const data = await listModules();
@@ -44,7 +46,6 @@ const ModuleList = () => {
const handleDelete = async () => {
if (!moduleToDelete) return;
const success = await deleteModule(moduleToDelete.id);
if (success) {
fetchModules();
@@ -106,11 +107,36 @@ const ModuleList = () => {
<p className="text-sm text-(--text-secondary)">{module.module_id}</p>
</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' ? (
<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>
@@ -79,7 +79,7 @@ export const useModuleApi = (): UseModuleApiResult => {
[]);
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) => {
+2 -2
View File
@@ -129,8 +129,8 @@ const ProfilePage: React.FC = () => {
return;
}
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
setPasswordError(t('messages.passwordMismatch'));
if (passwordForm.newPassword === passwordForm.confirmPassword) {
setPasswordError("New password must be different from your current password");
return;
}
@@ -467,7 +467,7 @@ const AllRoles = () => {
footer={
<>
<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 { 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 { CustomActionMenu, CustomActionItem, CustomStatus } from "../../../components/custom";
import { CustomActionMenu, CustomActionItem, CustomStatus, CustomConfirmationModal } from "../../../components/custom";
import { Loader } from "../../../components/custom/CustomLoader";
import { paletteApi } from "../PaletteApi";
import type { ColorPalette } from "../ThemeTypes";
@@ -18,6 +17,8 @@ const AllPalettes: React.FC = () => {
const [errorMessage, setErrorMessage] = useState("");
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingPalette, setEditingPalette] = useState<ColorPalette | null>(null);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [deleteId, setDeleteId] = useState<string | null>(null);
const { refreshTheme } = useTheme();
const fetchPalettes = async () => {
@@ -49,14 +50,22 @@ const AllPalettes: React.FC = () => {
setIsModalOpen(true);
};
const handleDelete = async (id: string) => {
if (!window.confirm(t('theme:confirmDelete'))) return;
const handleDelete = (id: string, isDefault: boolean) => {
if (isDefault) return;
setDeleteId(id);
setIsDeleteModalOpen(true);
};
const handleConfirmDelete = async () => {
if (!deleteId) return;
try {
await paletteApi.deletePalette(id);
toast.success(t('theme:success.deleted'));
await paletteApi.deletePalette(deleteId);
fetchPalettes();
} 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 {
if (editingPalette) {
await paletteApi.updatePalette(editingPalette.id, data);
toast.success(t('theme:success.updated'));
} else {
await paletteApi.createPalette(data);
toast.success(t('theme:success.created'));
}
setIsModalOpen(false);
fetchPalettes();
@@ -136,7 +143,7 @@ const AllPalettes: React.FC = () => {
</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"}`}
>
<div
@@ -155,48 +162,62 @@ const AllPalettes: React.FC = () => {
),
},
],
[]
[t]
);
return (
<div className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold text-(--text-primary)">{t('theme:title')}</h1>
{/* <p className="text-sm text-(--text-secondary) mt-1">
Manage system color themes and default palettes.
</p> */}
<>
<div className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold text-(--text-primary)">{t('theme:title')}</h1>
{/* <p className="text-sm text-(--text-secondary) mt-1">
Manage system color themes and default palettes.
</p> */}
</div>
<CustomButton onClick={handleCreate} leftIcon={<Plus size={18} />}>
{t('theme:actions.create')}
</CustomButton>
</div>
<CustomButton onClick={handleCreate} leftIcon={<Plus size={18} />}>
{t('theme:actions.create')}
</CustomButton>
{isLoading ? (
<div className="rounded-lg border border-[var(--card-border)] bg-[var(--card-bg)] p-6">
<Loader />
</div>
) : errorMessage ? (
<div className="rounded-lg border border-red-200 bg-red-50 p-6 text-sm text-red-600">
{errorMessage}
</div>
) : (
<DataTable
data={palettes}
columns={columns}
enableSearchDropdown={true}
search="name"
buildSuggestionLabel={(row) => row.name}
/>
)}
<PaletteForm
isOpen={isModalOpen}
initialData={editingPalette}
onSubmit={handleSubmit}
onCancel={() => setIsModalOpen(false)}
isLoading={isLoading}
/>
</div>
{isLoading ? (
<div className="rounded-lg border border-[var(--card-border)] bg-[var(--card-bg)] p-6">
<Loader />
</div>
) : errorMessage ? (
<div className="rounded-lg border border-red-200 bg-red-50 p-6 text-sm text-red-600">
{errorMessage}
</div>
) : (
<DataTable
data={palettes}
columns={columns}
enableSearchDropdown={true}
search="name"
/>
)}
<PaletteForm
isOpen={isModalOpen}
initialData={editingPalette}
onSubmit={handleSubmit}
onCancel={() => setIsModalOpen(false)}
<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}
/>
</div>
</>
);
};
@@ -14,6 +14,7 @@ import {
CustomActionMenu,
CustomActionItem,
CustomStatus,
CustomPhoneInput,
} from "../../../components/custom";
import type { ColumnDef } from "../../../components/custom/CustomTable";
import type {
@@ -281,6 +282,10 @@ const AllUsers = () => {
[]
);
const handleEditPhoneChange = useCallback((val: string | undefined) => {
setEditForm((prev) => ({ ...prev, phone_number: val || "" }));
}, []);
const handleStatusChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
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.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.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 />
<CustomDropdown
label={t('fields.role')}
+2 -1
View File
@@ -20,6 +20,7 @@ import CustomIncrement from "./CustomIncrement";
import CustomLoader from "./CustomLoader";
import CustomActionMenu, { CustomActionItem } from "./CustomActionMenu";
import CustomStatus from "./CustomStatus";
import { CustomPhoneInput } from "./CustomPhoneInput";
export {
CustomInput,
@@ -44,5 +45,5 @@ export {
CustomActionMenu,
CustomActionItem,
CustomStatus,
CustomPhoneInput,
};
+6
View File
@@ -63,5 +63,11 @@
"sectionTitle": "تكوين الألوان",
"required": "مطلوب"
}
},
"deleteModal": {
"title": "حذف النظام",
"message": "هل أنت متأكد أنك تريد حذف هذا النظام؟",
"confirm": "حذف",
"cancel": "إلغاء"
}
}
+7 -1
View File
@@ -63,5 +63,11 @@
"sectionTitle": "Color Configuration",
"required": "Required"
}
}
},
"deleteModal": {
"title": "Delete Palette",
"message": "Are you sure you want to delete this palette?",
"confirm": "Delete",
"cancel": "Cancel"
}
}