Removed duplicate filter in settings page
Improved Rule value handling
This commit is contained in:
@@ -582,14 +582,15 @@ export const USER_ROLE_OPTIONS = [
|
||||
{ value: 'Supervisor', label: 'Supervisor' },
|
||||
{ value: 'Manager', label: 'Manager' },
|
||||
{ value: 'Contributor', label: 'Contributor' },
|
||||
{ value: 'Unassigned', label: 'Unassigned' },
|
||||
];
|
||||
|
||||
export const getUserRoleBadge = (role?: string | number | UserRole | null) => {
|
||||
if (role === null || role === undefined || role === '') {
|
||||
if (role === null || role === undefined || role === '' || String(role).trim().toLowerCase() === 'null' || String(role).trim().toLowerCase() === 'unassigned') {
|
||||
return {
|
||||
label: 'Contributor',
|
||||
className: 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30',
|
||||
dot: 'bg-emerald-400',
|
||||
label: 'Unassigned',
|
||||
className: 'bg-slate-800/80 text-slate-400 border-slate-700/80 shadow-xs',
|
||||
dot: 'bg-slate-500',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,6 @@ import {
|
||||
getUserSyncRecord,
|
||||
getInvites,
|
||||
sendInvite,
|
||||
getApplicantInvite,
|
||||
getApplicantInvites,
|
||||
getOrganisations,
|
||||
getAwards,
|
||||
getContracts,
|
||||
@@ -131,10 +129,11 @@ export const SettingsPage: React.FC = () => {
|
||||
|
||||
const filteredUsers = useMemo(() => {
|
||||
return usersList.filter((u) => {
|
||||
const roleLabel = getUserRoleBadge(u.role).label;
|
||||
const matchRole =
|
||||
userRoleFilter === 'all' ||
|
||||
(u.role && String(u.role).toLowerCase() === userRoleFilter.toLowerCase()) ||
|
||||
(!u.role && userRoleFilter.toLowerCase() === 'contributor');
|
||||
roleLabel.toLowerCase() === userRoleFilter.toLowerCase() ||
|
||||
(u.role && String(u.role).toLowerCase() === userRoleFilter.toLowerCase());
|
||||
return matchRole;
|
||||
});
|
||||
}, [usersList, userRoleFilter]);
|
||||
@@ -198,9 +197,6 @@ export const SettingsPage: React.FC = () => {
|
||||
// 2. Communications & Invites State
|
||||
// ==========================================
|
||||
const [isSendInviteModalOpen, setIsSendInviteModalOpen] = useState(false);
|
||||
const [applicantSearchId, setApplicantSearchId] = useState('');
|
||||
const [applicantInvitesResults, setApplicantInvitesResults] = useState<Invite[] | null>(null);
|
||||
const [searchingApplicant, setSearchingApplicant] = useState(false);
|
||||
|
||||
// Invite Form State
|
||||
const [inviteOrgId, setInviteOrgId] = useState<string | number>('');
|
||||
@@ -266,37 +262,6 @@ export const SettingsPage: React.FC = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const handleSearchApplicantInvites = async (e?: React.FormEvent) => {
|
||||
if (e) e.preventDefault();
|
||||
if (!applicantSearchId.trim()) return;
|
||||
try {
|
||||
setSearchingApplicant(true);
|
||||
const res = await getApplicantInvites(applicantSearchId.trim());
|
||||
setApplicantInvitesResults(res);
|
||||
success('Lookup Complete', `Retrieved ${res.length} invitation records for applicant.`);
|
||||
} catch (err: any) {
|
||||
toastError('Lookup Error', err);
|
||||
setApplicantInvitesResults([]);
|
||||
} finally {
|
||||
setSearchingApplicant(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLookupSingleApplicantInvite = async () => {
|
||||
if (!applicantSearchId.trim()) return;
|
||||
try {
|
||||
setSearchingApplicant(true);
|
||||
const res = await getApplicantInvite(applicantSearchId.trim());
|
||||
setApplicantInvitesResults(res ? [res] : []);
|
||||
success('Primary Invite Found', `Retrieved primary invitation for applicant.`);
|
||||
} catch (err: any) {
|
||||
toastError('Lookup Error', err);
|
||||
setApplicantInvitesResults([]);
|
||||
} finally {
|
||||
setSearchingApplicant(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 3. Email Gateway & Notifications State
|
||||
// ==========================================
|
||||
@@ -908,71 +873,7 @@ export const SettingsPage: React.FC = () => {
|
||||
{/* TAB 2: Communications & Invites */}
|
||||
{/* ============================================================== */}
|
||||
{activeTab === 'invites' && (
|
||||
<div className="space-y-6">
|
||||
{/* Applicant Invites Lookup */}
|
||||
<div className="bg-[#0f1523]/80 backdrop-blur-md p-5 rounded-2xl border border-slate-800/80 shadow-xl">
|
||||
<h3 className="text-sm font-bold text-white mb-1">Applicant Invitations Lookup</h3>
|
||||
<p className="text-xs text-slate-400 mb-3">
|
||||
Query invitations dispatched to a specific candidate or grantee using their unique Applicant User UUID.
|
||||
</p>
|
||||
<form onSubmit={handleSearchApplicantInvites} className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={applicantSearchId}
|
||||
onChange={(e) => setApplicantSearchId(e.target.value)}
|
||||
placeholder="Enter Applicant User ID (UUID)..."
|
||||
className="flex-1 px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/50"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={searchingApplicant || !applicantSearchId.trim()}
|
||||
className="px-3.5 py-2 text-xs font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-xl transition cursor-pointer disabled:opacity-50"
|
||||
title="Lookup all invites"
|
||||
>
|
||||
{searchingApplicant ? 'Searching...' : 'All Invites'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLookupSingleApplicantInvite}
|
||||
disabled={searchingApplicant || !applicantSearchId.trim()}
|
||||
className="px-3.5 py-2 text-xs font-semibold text-blue-300 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded-xl transition cursor-pointer disabled:opacity-50"
|
||||
title="Lookup primary invite"
|
||||
>
|
||||
Primary Invite
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{applicantInvitesResults && (
|
||||
<div className="mt-4 pt-4 border-t border-slate-800/80">
|
||||
<span className="text-xs font-semibold text-slate-300">
|
||||
Lookup Results ({applicantInvitesResults.length} matches):
|
||||
</span>
|
||||
{applicantInvitesResults.length === 0 ? (
|
||||
<p className="text-xs text-slate-500 mt-2">No invitations found for this applicant UUID.</p>
|
||||
) : (
|
||||
<div className="mt-2 space-y-2">
|
||||
{applicantInvitesResults.map((inv, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="p-3 bg-slate-900/60 rounded-xl border border-slate-800 text-xs flex flex-col sm:flex-row sm:items-center justify-between gap-2"
|
||||
>
|
||||
<div>
|
||||
<span className="font-semibold text-white">{inv.message}</span>
|
||||
<div className="text-[11px] text-slate-400 mt-0.5">
|
||||
Org #{inv.organisationId} • Award #{inv.awardId} • Contract #{inv.contractId}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-blue-500/10 text-blue-400 border border-blue-500/20">
|
||||
{getNotificationPlatformLabel(inv.platform)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Standardized Toolbar & Filter Bar directly above Sent Invitations Table */}
|
||||
<div className="bg-[#0f1523]/80 backdrop-blur-md p-3.5 sm:p-4 rounded-2xl border border-slate-800/80 shadow-xl flex flex-col lg:flex-row items-stretch lg:items-center justify-between gap-3">
|
||||
{/* Filters on the left */}
|
||||
|
||||
Reference in New Issue
Block a user