Feat: Auto-check all assets by default, show unified asset count in table, display read-only inherited assets in modal

This commit is contained in:
kenilkb 2026-07-16 12:30:11 +05:30
parent d30d8d6b1e
commit fa781d08f5
3 changed files with 90 additions and 34 deletions

View File

@ -59,7 +59,7 @@ export class AuthController {
}).parse(req.body);
const result = await this.authService.updatePartner(partnerId, {
partnerGroup: partnerGroup === null ? undefined : partnerGroup,
partnerGroup: (partnerGroup === null || partnerGroup === '') ? null : partnerGroup,
assignedNdaId: assignedNdaId === null ? undefined : assignedNdaId,
assignedMsaId: assignedMsaId === null ? undefined : assignedMsaId,
sharedAssetIds,

View File

@ -137,7 +137,7 @@ export class AuthService {
return { inviteToken, emailSent, emailError };
}
public async updatePartner(partnerId: string, options: { partnerGroup?: string, assignedNdaId?: string, assignedMsaId?: string, sharedAssetIds?: string[], mfaEnabled?: boolean }) {
public async updatePartner(partnerId: string, options: { partnerGroup?: string | null, assignedNdaId?: string | null, assignedMsaId?: string | null, sharedAssetIds?: string[], mfaEnabled?: boolean }) {
const user = await prisma.user.findUnique({
where: { id: partnerId }
});

View File

@ -15,6 +15,7 @@ import {
Copy,
Trash2,
Plus,
Lock,
} from "lucide-react";
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
@ -154,7 +155,10 @@ export const DirectoryPage: React.FC = () => {
const [partnerAssetToRemove, setPartnerAssetToRemove] = useState<{ id: string, title: string } | null>(null);
useEffect(() => {
getAssets().then(setAllAssets).catch(console.error);
getAssets().then((assets) => {
setAllAssets(assets);
setSelectedAssetIds(assets.map(a => a.id));
}).catch(console.error);
getLegalDocuments().then(setAllDocs).catch(console.error);
getAssetGroups().then(setAssetGroups).catch(console.error);
}, []);
@ -560,6 +564,12 @@ export const DirectoryPage: React.FC = () => {
onClick={() => {
setInviteResult(null);
setEmail("");
setPartnerGroup("");
setAssignedNdaId("");
setAssignedMsaId("");
setMfaRequired(true);
setSelectedAssetIds(allAssets.map(a => a.id));
setInviteAssetSearch("");
setIsInviteOpen(true);
}}
variant="primary"
@ -740,6 +750,7 @@ export const DirectoryPage: React.FC = () => {
) : (
paginatedPartners.map((partner) => {
const sc = getStatusConfig(partner.onboardingStatus);
const pGroup = partner.partnerGroup;
return (
<tr
key={partner.id}
@ -772,9 +783,16 @@ export const DirectoryPage: React.FC = () => {
setSelectedPartnerForAssets(partner);
setIsPartnerAssetsOpen(true);
}}
className="text-xs font-bold text-ink-700 hover:text-ink-950 hover:underline cursor-pointer focus:outline-none"
className="text-xs font-bold text-ink-700 hover:text-ink-950 hover:underline cursor-pointer focus:outline-none flex flex-col items-start gap-0.5"
>
{partner.sharedAssets?.length || 0} assets
<span>
{((partner.sharedAssets?.length || 0) + (pGroup ? (assetGroups.find(g => g.name.trim().toLowerCase() === pGroup.trim().toLowerCase())?.assets?.length || 0) : 0))} assets
</span>
{pGroup && assetGroups.some(g => g.name.trim().toLowerCase() === pGroup.trim().toLowerCase()) && (
<span className="text-[9px] text-amber-600 font-extrabold uppercase tracking-wide">
Includes {pGroup} Group
</span>
)}
</button>
</td>
<td className="px-5 py-4">
@ -971,19 +989,30 @@ export const DirectoryPage: React.FC = () => {
</div>
) : (
/* Current Shared Assets List */
(!selectedPartnerForAssets?.sharedAssets || selectedPartnerForAssets.sharedAssets.length === 0) ? (
<div className="text-center py-10 bg-ink-50/50 border border-dashed border-ink-200 rounded-xl">
<Folder className="w-10 h-10 text-ink-300 mx-auto mb-2" />
<p className="text-xs font-bold text-ink-900 font-sans">No assets shared</p>
<p className="text-[10px] text-ink-450 mt-1 font-sans">There are no assets currently assigned to this partner.</p>
</div>
) : (
<div className="border border-ink-200 rounded-xl overflow-hidden divide-y divide-ink-200 bg-ink-0 max-h-[350px] overflow-y-auto">
{selectedPartnerForAssets.sharedAssets.map((item: any) => {
const asset = item.asset;
if (!asset) return null;
return (
<div key={item.assetId} className="flex items-center justify-between p-3 hover:bg-ink-100 transition-colors font-sans">
/* Current Shared Assets List */
(() => {
const matchedGroup = selectedPartnerForAssets?.partnerGroup
? assetGroups.find(g => g.name.trim().toLowerCase() === selectedPartnerForAssets.partnerGroup.trim().toLowerCase())
: null;
const inheritedAssets = matchedGroup ? matchedGroup.assets : [];
const directAssets = selectedPartnerForAssets?.sharedAssets || [];
const totalCount = directAssets.length + inheritedAssets.length;
if (totalCount === 0) {
return (
<div className="text-center py-10 bg-ink-50/50 border border-dashed border-ink-200 rounded-xl">
<Folder className="w-10 h-10 text-ink-300 mx-auto mb-2" />
<p className="text-xs font-bold text-ink-900 font-sans">No assets shared</p>
<p className="text-[10px] text-ink-450 mt-1 font-sans">There are no assets currently assigned to this partner.</p>
</div>
);
}
return (
<div className="border border-ink-200 rounded-xl overflow-hidden divide-y divide-ink-200 bg-ink-0 max-h-[350px] overflow-y-auto">
{/* Group Inherited Assets */}
{inheritedAssets.map((asset: any) => (
<div key={`group-asset-${asset.id}`} className="flex items-center justify-between p-3 hover:bg-ink-100 transition-colors font-sans">
<div className="flex items-center gap-3 min-w-0">
{getFileIcon(asset.type || '', asset.title)}
<div className="min-w-0 font-sans">
@ -992,26 +1021,53 @@ export const DirectoryPage: React.FC = () => {
<span className="text-[8px] px-1.5 py-0.2 rounded bg-ink-100 border border-ink-200 text-ink-600 font-bold uppercase tracking-wider">
{asset.categoryId || 'General'}
</span>
{asset.subcategory && (
<span className="text-[8px] px-1.5 py-0.2 rounded bg-ink-50 border border-ink-150 text-ink-500 font-semibold">
{asset.subcategory}
</span>
)}
<span className="text-[8px] px-1.5 py-0.2 rounded bg-amber-500/10 border border-amber-500/20 text-amber-700 font-extrabold uppercase tracking-wider">
Inherited ({selectedPartnerForAssets?.partnerGroup})
</span>
</div>
</div>
</div>
<button
onClick={() => handleRemoveAssetFromPartner(item.assetId, asset.title)}
className="p-1.5 text-red-500 hover:text-red-750 hover:bg-red-500/10 rounded-lg transition-colors cursor-pointer shrink-0 ml-4"
title="Remove Share"
>
<Trash2 className="w-4 h-4" />
</button>
<div className="p-1.5 text-ink-400 cursor-not-allowed shrink-0 ml-4" title="Inherited via group permissions (Read-only)">
<Lock className="w-4 h-4 text-ink-400" />
</div>
</div>
);
})}
</div>
)
))}
{/* Direct Shared Assets */}
{directAssets.map((item: any) => {
const asset = item.asset;
if (!asset) return null;
return (
<div key={item.assetId} className="flex items-center justify-between p-3 hover:bg-ink-100 transition-colors font-sans">
<div className="flex items-center gap-3 min-w-0">
{getFileIcon(asset.type || '', asset.title)}
<div className="min-w-0 font-sans">
<p className="text-xs font-bold text-ink-900 truncate">{asset.title}</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-[8px] px-1.5 py-0.2 rounded bg-ink-100 border border-ink-200 text-ink-600 font-bold uppercase tracking-wider">
{asset.categoryId || 'General'}
</span>
{asset.subcategory && (
<span className="text-[8px] px-1.5 py-0.2 rounded bg-ink-50 border border-ink-150 text-ink-500 font-semibold">
{asset.subcategory}
</span>
)}
</div>
</div>
</div>
<button
onClick={() => handleRemoveAssetFromPartner(item.assetId, asset.title)}
className="p-1.5 text-red-500 hover:text-red-750 hover:bg-red-500/10 rounded-lg transition-colors cursor-pointer shrink-0 ml-4"
title="Remove Share"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
);
})}
</div>
);
})()
)}
</div>
</Modal>