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 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) => {
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -63,5 +63,11 @@
|
||||
"sectionTitle": "تكوين الألوان",
|
||||
"required": "مطلوب"
|
||||
}
|
||||
},
|
||||
"deleteModal": {
|
||||
"title": "حذف النظام",
|
||||
"message": "هل أنت متأكد أنك تريد حذف هذا النظام؟",
|
||||
"confirm": "حذف",
|
||||
"cancel": "إلغاء"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user