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:
parent
d30d8d6b1e
commit
fa781d08f5
@ -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,
|
||||
|
||||
@ -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 }
|
||||
});
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user