diff --git a/src/features/dashboard/components/QuickActions.tsx b/src/features/dashboard/components/QuickActions.tsx
index 9ce3be4..976586b 100644
--- a/src/features/dashboard/components/QuickActions.tsx
+++ b/src/features/dashboard/components/QuickActions.tsx
@@ -6,6 +6,7 @@ import {
Settings,
Building2,
BadgeCheck,
+ AppWindow,
} from "lucide-react";
import { useAppSelector } from "@/hooks/redux-hooks";
import type { QuickAction } from "@/types/dashboard";
@@ -35,7 +36,7 @@ export const QuickActions = () => {
btncolor: "#4C89FA",
onClick: () => navigate("/tenants/create-wizard"),
},
- { icon: UserPlus, label: "Module", btncolor: "#16C784", onClick: () => navigate("/modules") },
+ { icon: AppWindow, label: "Module", btncolor: "#16C784", onClick: () => navigate("/modules") },
{
icon: Shield,
label: "Notification",
@@ -55,25 +56,25 @@ export const QuickActions = () => {
icon: UserPlus,
label: "New User",
btncolor: "#4C89FA",
- onClick: () => navigate("/tenant/users"),
+ onClick: () => navigate("/tenant/users", { state: { openCreateModal: true } }),
},
hasPermission("roles", "create") && {
icon: Shield,
label: "New Role",
btncolor: "#16C784",
- onClick: () => navigate("/tenant/roles"),
+ onClick: () => navigate("/tenant/roles", { state: { openCreateModal: true } }),
},
hasPermission("departments", "create") && {
icon: Building2,
label: "New Dept",
btncolor: "#FCA004",
- onClick: () => navigate("/tenant/departments"),
+ onClick: () => navigate("/tenant/departments", { state: { openCreateModal: true } }),
},
hasPermission("designations", "create") && {
icon: BadgeCheck,
label: "New Desig",
btncolor: "#6B7280",
- onClick: () => navigate("/tenant/designations"),
+ onClick: () => navigate("/tenant/designations", { state: { openCreateModal: true } }),
},
].filter(Boolean) as QuickAction[];
diff --git a/src/pages/superadmin/CreateTenantWizard.tsx b/src/pages/superadmin/CreateTenantWizard.tsx
index 62f8b7a..306a123 100644
--- a/src/pages/superadmin/CreateTenantWizard.tsx
+++ b/src/pages/superadmin/CreateTenantWizard.tsx
@@ -11,18 +11,18 @@ import {
PrimaryButton,
SecondaryButton,
MultiselectPaginatedSelect,
- AuthenticatedImage,
+ // AuthenticatedImage,
} from "@/components/shared";
import { tenantService } from "@/services/tenant-service";
import { moduleService } from "@/services/module-service";
-import { fileService } from "@/services/file-service";
+// import { fileService } from "@/services/file-service";
import { platformServiceApi, type PlatformService } from "@/services/platform-service";
import { showToast } from "@/utils/toast";
import {
ChevronRight,
ChevronLeft,
- Image as ImageIcon,
- X,
+ // Image as ImageIcon,
+ // X,
FileText,
GraduationCap,
AlertTriangle,
@@ -40,7 +40,7 @@ import {
Settings,
Loader2,
} from "lucide-react";
-import { generateUUID } from "@/lib/utils";
+// import { generateUUID } from "@/lib/utils";
// Icon mapping helper
const serviceIconMap: Record = {
@@ -311,7 +311,8 @@ const CreateTenantWizard = (): ReactElement => {
},
});
- // File upload state for branding
+ // File upload state for branding (commented out as company logo and favicon uploads are disabled during tenant creation)
+ /*
const [logoFile, setLogoFile] = useState(null);
const [faviconFile, setFaviconFile] = useState(null);
const [logoFileAttachmentUuid, setLogoFileAttachmentUuid] = useState<
@@ -330,6 +331,7 @@ const CreateTenantWizard = (): ReactElement => {
const [isUploadingFavicon, setIsUploadingFavicon] = useState(false);
const [logoError, setLogoError] = useState(null);
const [faviconError, setFaviconError] = useState(null);
+ */
// Auto-generate slug and domain from name
const nameValue = tenantDetailsForm.watch("name");
@@ -459,6 +461,7 @@ const CreateTenantWizard = (): ReactElement => {
}
};
+ /*
const handleDeleteLogo = (): void => {
if (logoPreviewUrl) {
URL.revokeObjectURL(logoPreviewUrl);
@@ -494,11 +497,13 @@ const CreateTenantWizard = (): ReactElement => {
fileInput.value = "";
}
};
+ */
const handleSubmit = async (): Promise => {
const isValid = await settingsForm.trigger();
if (!isValid) return;
+ /*
// Validate logo and favicon are uploaded
setLogoError(null);
setFaviconError(null);
@@ -518,6 +523,7 @@ const CreateTenantWizard = (): ReactElement => {
setCurrentStep(3); // Go to settings step where logo/favicon are
return;
}
+ */
try {
setIsSubmitting(true);
@@ -560,6 +566,8 @@ const CreateTenantWizard = (): ReactElement => {
};
const response = await tenantService.create(tenantData);
+
+ /*
const createdTenant = response.data;
const createdTenantId = createdTenant.id;
const slug = createdTenant.slug || tenantDetails.slug;
@@ -630,6 +638,7 @@ const CreateTenantWizard = (): ReactElement => {
};
await tenantService.update(createdTenantId, updateData);
}
+ */
const message = response.message || "Tenant created successfully";
showToast.success(message);
@@ -672,9 +681,9 @@ const CreateTenantWizard = (): ReactElement => {
const fieldName = path.replace("settings.branding.", "");
// Map file_path fields to form fields
if (fieldName === "logo_file_path") {
- setLogoError(detail.message);
+ // setLogoError(detail.message);
} else if (fieldName === "favicon_file_path") {
- setFaviconError(detail.message);
+ // setFaviconError(detail.message);
} else {
settingsForm.setError(fieldName as keyof SettingsForm, {
type: "server",
@@ -1288,9 +1297,9 @@ const CreateTenantWizard = (): ReactElement => {
- {/* Logo and Favicon Upload */}
+ {/* Logo and Favicon Upload commented out since file attachment bucket can only be set after tenant creation */}
+ {/*
- {/* Company Logo */}
Company Logo *
@@ -1317,19 +1326,16 @@ const CreateTenantWizard = (): ReactElement => {
onChange={async (e) => {
const file = e.target.files?.[0];
if (file) {
- // Clean up previous preview URL if exists
if (logoPreviewUrl) {
URL.revokeObjectURL(logoPreviewUrl);
}
- // Validate file size (2MB max)
if (file.size > 2 * 1024 * 1024) {
showToast.error(
"Logo file size must be less than 2MB",
);
return;
}
- // Validate file type
const validTypes = [
"image/png",
"image/svg+xml",
@@ -1342,7 +1348,6 @@ const CreateTenantWizard = (): ReactElement => {
);
return;
}
- // Create local preview URL immediately
const previewUrl = URL.createObjectURL(file);
setLogoFile(file);
setLogoPreviewUrl(previewUrl);
@@ -1388,7 +1393,6 @@ const CreateTenantWizard = (): ReactElement => {
)}
- {/* Favicon */}
Favicon *
@@ -1415,19 +1419,16 @@ const CreateTenantWizard = (): ReactElement => {
onChange={async (e) => {
const file = e.target.files?.[0];
if (file) {
- // Clean up previous preview URL if exists
if (faviconPreviewUrl) {
URL.revokeObjectURL(faviconPreviewUrl);
}
- // Validate file size (500KB max)
if (file.size > 500 * 1024) {
showToast.error(
"Favicon file size must be less than 500KB",
);
return;
}
- // Validate file type
const validTypes = [
"image/x-icon",
"image/png",
@@ -1439,7 +1440,6 @@ const CreateTenantWizard = (): ReactElement => {
);
return;
}
- // Create local preview URL immediately
const previewUrl = URL.createObjectURL(file);
setFaviconFile(file);
setFaviconPreviewUrl(previewUrl);
@@ -1485,6 +1485,7 @@ const CreateTenantWizard = (): ReactElement => {
)}
+ */}
{/* Primary Color */}
diff --git a/src/pages/tenant/Departments.tsx b/src/pages/tenant/Departments.tsx
index ea6e37b..d34f48b 100644
--- a/src/pages/tenant/Departments.tsx
+++ b/src/pages/tenant/Departments.tsx
@@ -1,4 +1,4 @@
-import { useRef, type ReactElement } from "react";
+import { useRef, useEffect, type ReactElement } from "react";
import { Layout } from "@/components/layout/Layout";
import {
DepartmentsTable,
@@ -7,10 +7,21 @@ import {
import { PrimaryButton } from "@/components/shared";
import { Plus } from "lucide-react";
import { usePermissions } from "@/hooks/usePermissions";
+import { useLocation } from "react-router-dom";
const Departments = (): ReactElement => {
const tableRef = useRef
(null);
const { canCreate } = usePermissions();
+ const location = useLocation();
+
+ useEffect(() => {
+ if (location.state?.openCreateModal) {
+ window.history.replaceState({}, document.title);
+ setTimeout(() => {
+ tableRef.current?.openNewModal();
+ }, 100);
+ }
+ }, [location.state]);
return (
{
const tableRef = useRef(null);
const { canCreate } = usePermissions();
+ const location = useLocation();
+
+ useEffect(() => {
+ if (location.state?.openCreateModal) {
+ window.history.replaceState({}, document.title);
+ setTimeout(() => {
+ tableRef.current?.openNewModal();
+ }, 100);
+ }
+ }, [location.state]);
return (
{
+ const tableRef = useRef(null);
+ const location = useLocation();
+
+ useEffect(() => {
+ if (location.state?.openCreateModal) {
+ window.history.replaceState({}, document.title);
+ setTimeout(() => {
+ tableRef.current?.openNewModal();
+ }, 100);
+ }
+ }, [location.state]);
+
return (
{
description: "Define and manage roles to control user access based on job responsibilities",
}}
>
-
+
);
};
diff --git a/src/pages/tenant/StorageDashboard.tsx b/src/pages/tenant/StorageDashboard.tsx
index bf1f857..a688467 100644
--- a/src/pages/tenant/StorageDashboard.tsx
+++ b/src/pages/tenant/StorageDashboard.tsx
@@ -29,6 +29,7 @@ import {
// SecondaryButton,
} from "@/components/shared";
import { useAppTheme } from "@/hooks/useAppTheme";
+import { showToast } from "@/utils/toast";
// ─────────────────────────────────────────────────────────────────────────────
// Helpers
@@ -83,8 +84,11 @@ const QuotaEditModal = ({
});
onUpdated();
onClose();
- } catch (err) {
- alert("Failed to update quota");
+ showToast.success("Quota updated successfully");
+ } catch (err: any) {
+ showToast.error(
+ err?.response?.data?.error?.message || "Failed to update quota",
+ );
} finally {
setIsUpdating(false);
}
@@ -114,22 +118,22 @@ const QuotaEditModal = ({
>
}
>
-
- setMaxStorageGB(parseInt(e.target.value) || 0)}
- placeholder="e.g. 10 for 10GB"
- />
- setMaxFileMB(parseInt(e.target.value) || 0)}
- placeholder="e.g. 50 for 50MB"
- />
-
+ {/* */}
+ setMaxStorageGB(parseInt(e.target.value) || 1)}
+ placeholder="e.g. 10 for 10GB"
+ />
+ setMaxFileMB(parseInt(e.target.value) || 1)}
+ placeholder="e.g. 50 for 50MB"
+ />
+ {/*
*/}
);
};
@@ -137,7 +141,9 @@ const QuotaEditModal = ({
const StorageDashboard = (): ReactElement => {
const { primaryColor } = useAppTheme();
- const [activeTab, setActiveTab] = useState<"stats" | "quota" | "cleanup">("stats");
+ const [activeTab, setActiveTab] = useState<"stats" | "quota" | "cleanup">(
+ "stats",
+ );
const [stats, setStats] = useState(null);
const [quota, setQuota] = useState(null);
const [loading, setLoading] = useState(true);
@@ -157,7 +163,8 @@ const StorageDashboard = (): ReactElement => {
setPurgeResult(null);
setPurgeError(null);
try {
- const res = await fileAttachmentService.purgeSoftDeletedBlobs(olderThanHours);
+ const res =
+ await fileAttachmentService.purgeSoftDeletedBlobs(olderThanHours);
if (res.success) {
setPurgeResult(res.data.purged);
// Refresh usage stats and quota following successful purge
@@ -167,7 +174,8 @@ const StorageDashboard = (): ReactElement => {
}
} catch (err: any) {
setPurgeError(
- err?.response?.data?.error?.message || "Failed to purge unreferenced files."
+ err?.response?.data?.error?.message ||
+ "Failed to purge unreferenced files.",
);
console.error("Purge error:", err);
} finally {
@@ -183,7 +191,7 @@ const StorageDashboard = (): ReactElement => {
fileAttachmentService.getQuota(),
]);
console.log(quotaRes.data);
-
+
setStats(statsRes.data);
setQuota(quotaRes.data);
} catch (err: any) {
@@ -516,9 +524,7 @@ const StorageDashboard = (): ReactElement => {
-
+
Storage Maintenance & Cleanup
@@ -526,22 +532,30 @@ const StorageDashboard = (): ReactElement => {
- When files are deleted, the system decrements their reference counts. Files marked as{" "}
- soft-deleted with 0 active references are preserved temporarily on the storage provider
- to allow background services (like AI embeddings, audits, or indexes) to finish processing them.
+ When files are deleted, the system decrements their
+ reference counts. Files marked as{" "}
+ soft-deleted with 0 active references are
+ preserved temporarily on the storage provider to allow
+ background services (like AI embeddings, audits, or indexes)
+ to finish processing them.
- Use this administrative tool to permanently purge these unreferenced physical files from the storage provider and reclaim space.
+ Use this administrative tool to permanently purge these
+ unreferenced physical files from the storage provider and
+ reclaim space.
-
Warning: Permanent Action
+
+ Warning: Permanent Action
+
- Purging files is irreversible. Once deleted from the physical storage provider (local disk, Azure, or AWS),
- the file binaries cannot be recovered.
+ Purging files is irreversible. Once deleted from the
+ physical storage provider (local disk, Azure, or AWS), the
+ file binaries cannot be recovered.
@@ -551,18 +565,25 @@ const StorageDashboard = (): ReactElement => {
label="Purge Files Older Than (Hours)"
type="number"
value={olderThanHours}
- onChange={(e) => setOlderThanHours(Math.max(0, parseInt(e.target.value) || 0))}
+ onChange={(e) =>
+ setOlderThanHours(
+ Math.max(0, parseInt(e.target.value) || 0),
+ )
+ }
placeholder="e.g. 24 (0 = purge all eligible immediately)"
/>
- Set to 0 to instantly purge all unreferenced soft-deleted blobs, or specify hours (e.g., 24) to keep files deleted within that timeframe.
+ Set to 0 to instantly purge all unreferenced soft-deleted
+ blobs, or specify hours (e.g., 24) to keep files deleted
+ within that timeframe.
{purgeResult !== null && (
- Successfully purged {purgeResult} orphaned file binary/binaries, freeing up storage space!
+ Successfully purged {purgeResult} {" "}
+ orphaned file binary/binaries, freeing up storage space!
)}
@@ -625,10 +646,13 @@ const StorageDashboard = (): ReactElement => {
-
Are you absolutely sure?
+
+ Are you absolutely sure?
+
- This will permanently delete all unreferenced physical file binaries older than{" "}
- {olderThanHours} hours from your storage provider. This action is irreversible.
+ This will permanently delete all unreferenced physical file
+ binaries older than {olderThanHours} hours from
+ your storage provider. This action is irreversible.
diff --git a/src/pages/tenant/Tasks.tsx b/src/pages/tenant/Tasks.tsx
index 11be115..19f5ec7 100644
--- a/src/pages/tenant/Tasks.tsx
+++ b/src/pages/tenant/Tasks.tsx
@@ -137,14 +137,36 @@ const Tasks = (): ReactElement => {
key: "assignment",
label: "Assigned To",
render: (task) => {
- const user = task.assignment.assigned_to_name;
+ const assignedUsers = task.assignment.assigned_users || [];
const roleIds = task.assignment.assigned_role_ids;
+
+ if (assignedUsers.length > 0) {
+ if (assignedUsers.length === 1) {
+ const u = assignedUsers[0];
+ const text = u.name ? `${u.email} (${u.name})` : u.email;
+ return
{text} ;
+ } else {
+ const firstUser = assignedUsers[0];
+ const displayText = firstUser.name
+ ? `${firstUser.email} (${firstUser.name}) ...`
+ : `${firstUser.email} ...`;
+ const allUsersTooltip = assignedUsers.map((u) => u.name ? `${u.email} (${u.name})` : u.email).join("\n");
+ return (
+
+ {displayText}
+
+ );
+ }
+ }
+
return (
- {user ||
- (roleIds && roleIds.length > 0
- ? `${roleIds.length} roles`
- : "-")}
+ {roleIds && roleIds.length > 0
+ ? `${roleIds.length} roles`
+ : "-"}
);
},
diff --git a/src/pages/tenant/Users.tsx b/src/pages/tenant/Users.tsx
index e7f4646..c0f79ea 100644
--- a/src/pages/tenant/Users.tsx
+++ b/src/pages/tenant/Users.tsx
@@ -1,13 +1,24 @@
-import { useRef, type ReactElement } from "react";
+import { useRef, useEffect, type ReactElement } from "react";
import { Layout } from "@/components/layout/Layout";
import { UsersTable, type UsersTableRef } from "@/components/superadmin";
import { PrimaryButton } from "@/components/shared";
import { Plus } from "lucide-react";
import { usePermissions } from "@/hooks/usePermissions";
+import { useLocation } from "react-router-dom";
const Users = (): ReactElement => {
const tableRef = useRef
(null);
const { canCreate } = usePermissions();
+ const location = useLocation();
+
+ useEffect(() => {
+ if (location.state?.openCreateModal) {
+ window.history.replaceState({}, document.title);
+ setTimeout(() => {
+ tableRef.current?.openNewModal();
+ }, 100);
+ }
+ }, [location.state]);
return (
| null;
assigned_role_ids?: string[] | null;
assigned_at: string;
};