Collapsed inbound/outbound labels for linked files in dashboard

- Collapsed inbound/outbound labels for linked files in dashboard
This commit is contained in:
harmj0y
2025-09-26 11:39:44 -07:00
parent d9739c70ea
commit 4c828ffc09
2 changed files with 117 additions and 93 deletions
@@ -756,6 +756,25 @@ const FileUpload: React.FC = () => {
</div>
)}
<button
type="submit"
disabled={isUploading || fileStatuses.length === 0 || !project}
className={`w-full py-2 px-4 rounded-md text-white font-medium transition-colors ${
isUploading || fileStatuses.length === 0 || !project
? 'bg-gray-400 dark:bg-gray-600 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600'
}`}
>
{isUploading ? (
<div className="flex items-center justify-center">
<Loader2 className="w-5 h-5 animate-spin mr-2" />
Uploading {pendingFiles.length} file{pendingFiles.length !== 1 ? 's' : ''}...
</div>
) : (
`Upload ${fileStatuses.length} File${fileStatuses.length !== 1 ? 's' : ''}`
)}
</button>
{successCount > 0 && (
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg flex items-center space-x-2">
<svg
@@ -775,25 +794,6 @@ const FileUpload: React.FC = () => {
</span>
</div>
)}
<button
type="submit"
disabled={isUploading || fileStatuses.length === 0 || !project}
className={`w-full py-2 px-4 rounded-md text-white font-medium transition-colors ${
isUploading || fileStatuses.length === 0 || !project
? 'bg-gray-400 dark:bg-gray-600 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600'
}`}
>
{isUploading ? (
<div className="flex items-center justify-center">
<Loader2 className="w-5 h-5 animate-spin mr-2" />
Uploading {pendingFiles.length} file{pendingFiles.length !== 1 ? 's' : ''}...
</div>
) : (
`Upload ${fileStatuses.length} File${fileStatuses.length !== 1 ? 's' : ''}`
)}
</button>
</form>
</div>
</div>
@@ -259,85 +259,109 @@ const LinkedFilesSection = ({ filePath, source }) => {
</CardHeader>
<CardContent>
<div className="space-y-3">
{linkedFiles.map((linking) => {
const linkedPath = getLinkedFilePath(linking, filePath);
const normalizedLinkedPath = linkedPath.replace(/\\/g, '/');
const direction = getRelationshipDirection(linking, filePath);
const fileName = linkedPath.split(/[\/\\]/).pop() || linkedPath;
// Get file status info - try both normalized and original path
let fileInfo = fileStatusMap.get(normalizedLinkedPath) || fileStatusMap.get(linkedPath);
const isCollected = fileInfo?.status === 'collected' && fileInfo?.object_id;
{(() => {
// Group linked files by path to merge entries with different directions
const groupedFiles = new Map();
return (
<div
key={linking.linking_id}
className={`flex items-start justify-between p-3 rounded-lg transition-colors ${
isCollected
? 'bg-green-50 dark:bg-green-900/20 hover:bg-green-100 dark:hover:bg-green-900/30 border border-green-200 dark:border-green-800'
: 'bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
>
<div className="flex items-start space-x-3 flex-1 min-w-0">
<div className="flex flex-col items-center flex-shrink-0">
<FileText className={`w-4 h-4 mt-1 ${
isCollected ? 'text-green-600 dark:text-green-400' : 'text-gray-500 dark:text-gray-400'
}`} />
{isCollected && (
<button
onClick={() => handleFileClick(linkedPath, fileInfo)}
className="text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-200 cursor-pointer mt-2"
title="Navigate to linked file"
>
<ExternalLink className="w-4 h-4" />
</button>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<p className={`text-sm font-medium truncate ${
isCollected ? 'text-green-900 dark:text-green-100' : 'text-gray-900 dark:text-gray-100'
}`}>
{fileName}
</p>
{linking.link_type && (
<span className="bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">
{linking.link_type}
</span>
)}
<span className={`text-xs px-2 py-1 rounded ${
direction === 'outbound'
? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200'
: 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200'
}`}>
{direction}
</span>
{fileInfo && !isCollected && (
<span className={`text-xs px-2 py-1 rounded ${
fileInfo.status === 'needs_to_be_collected'
? 'bg-yellow-200 dark:bg-yellow-800 text-yellow-900 dark:text-yellow-100'
: 'bg-red-200 dark:bg-red-800 text-red-900 dark:text-red-100'
}`}>
{fileInfo.status.replace(/_/g, ' ')}
</span>
linkedFiles.forEach((linking) => {
const linkedPath = getLinkedFilePath(linking, filePath);
const normalizedLinkedPath = linkedPath.replace(/\\/g, '/');
const direction = getRelationshipDirection(linking, filePath);
if (groupedFiles.has(normalizedLinkedPath)) {
const existing = groupedFiles.get(normalizedLinkedPath);
existing.directions.add(direction);
existing.linkTypes.add(linking.link_type);
} else {
groupedFiles.set(normalizedLinkedPath, {
linkedPath,
normalizedLinkedPath,
directions: new Set([direction]),
linkTypes: new Set([linking.link_type].filter(Boolean)),
linkingId: linking.linking_id
});
}
});
return Array.from(groupedFiles.values()).map((group) => {
const fileName = group.linkedPath.split(/[\/\\]/).pop() || group.linkedPath;
// Get file status info - try both normalized and original path
let fileInfo = fileStatusMap.get(group.normalizedLinkedPath) || fileStatusMap.get(group.linkedPath);
const isCollected = fileInfo?.status === 'collected' && fileInfo?.object_id;
return (
<div
key={group.linkingId}
className={`flex items-start justify-between p-3 rounded-lg transition-colors ${
isCollected
? 'bg-green-50 dark:bg-green-900/20 hover:bg-green-100 dark:hover:bg-green-900/30 border border-green-200 dark:border-green-800'
: 'bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
>
<div className="flex items-start space-x-3 flex-1 min-w-0">
<div className="flex flex-col items-center flex-shrink-0">
<FileText className={`w-4 h-4 mt-1 ${
isCollected ? 'text-green-600 dark:text-green-400' : 'text-gray-500 dark:text-gray-400'
}`} />
{isCollected && (
<button
onClick={() => handleFileClick(group.linkedPath, fileInfo)}
className="text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-200 cursor-pointer mt-2"
title="Navigate to linked file"
>
<ExternalLink className="w-4 h-4" />
</button>
)}
</div>
<p
className={`text-xs truncate mt-1 ${
isCollected
? 'text-green-600 dark:text-green-400 cursor-pointer hover:text-green-800 dark:hover:text-green-200 hover:underline'
: 'text-gray-500 dark:text-gray-400'
}`}
onClick={isCollected ? () => handleFileClick(linkedPath, fileInfo) : undefined}
title={isCollected ? "Click to navigate to file" : undefined}
>
{linkedPath}
</p>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<p className={`text-sm font-medium truncate ${
isCollected ? 'text-green-900 dark:text-green-100' : 'text-gray-900 dark:text-gray-100'
}`}>
{fileName}
</p>
{Array.from(group.linkTypes).map((linkType) => (
<span key={linkType} className="bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">
{linkType}
</span>
))}
{Array.from(group.directions).map((direction) => (
<span key={direction} className={`text-xs px-2 py-1 rounded ${
direction === 'outbound'
? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200'
: 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200'
}`}>
{direction}
</span>
))}
{fileInfo && !isCollected && (
<span className={`text-xs px-2 py-1 rounded ${
fileInfo.status === 'needs_to_be_collected'
? 'bg-yellow-200 dark:bg-yellow-800 text-yellow-900 dark:text-yellow-100'
: 'bg-red-200 dark:bg-red-800 text-red-900 dark:text-red-100'
}`}>
{fileInfo.status.replace(/_/g, ' ')}
</span>
)}
</div>
<p
className={`text-xs truncate mt-1 ${
isCollected
? 'text-green-600 dark:text-green-400 cursor-pointer hover:text-green-800 dark:hover:text-green-200 hover:underline'
: 'text-gray-500 dark:text-gray-400'
}`}
onClick={isCollected ? () => handleFileClick(group.linkedPath, fileInfo) : undefined}
title={isCollected ? "Click to navigate to file" : undefined}
>
{group.linkedPath}
</p>
</div>
</div>
</div>
</div>
);
})}
);
});
})()}
</div>
</CardContent>
</Card>