186 lines
6.5 KiB
TypeScript
186 lines
6.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Film } from 'lucide-react';
|
|
import { DesignMD, CompanyProfile, BrandAsset } from '../../types';
|
|
|
|
import { UnifiedMediaLibrary, UnifiedMediaItem } from '../content-grid/UnifiedMediaLibrary';
|
|
|
|
type AssetFilter = 'all' | 'image' | 'video' | 'audio';
|
|
type SourceFilter = 'all' | 'uploaded' | 'generated';
|
|
|
|
interface BrandTabMediaProps {
|
|
company: CompanyProfile;
|
|
designMD: DesignMD;
|
|
handleDesignChange: (key: keyof DesignMD, value: any) => void;
|
|
onEditAsset?: (type: keyof DesignMD, url: string) => void;
|
|
onSelectAsset?: (asset: BrandAsset) => void;
|
|
selectedAssetId?: string;
|
|
}
|
|
|
|
export const BrandTabMedia: React.FC<BrandTabMediaProps> = ({ company, designMD, handleDesignChange, onSelectAsset, selectedAssetId }) => {
|
|
const assets = designMD.brandAssets || [];
|
|
const [uploadingBrandAsset, setUploadingBrandAsset] = useState(false);
|
|
const [uploadingGenerated, setUploadingGenerated] = useState(false);
|
|
const [filterType, setFilterType] = useState<AssetFilter>('all');
|
|
const [filterSource, setFilterSource] = useState<SourceFilter>('all');
|
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
|
|
const handleBrandAssetFiles = async (files: File[]) => {
|
|
if (files.length === 0) return;
|
|
setUploadingBrandAsset(true);
|
|
try {
|
|
let currentWorkspacePath = '';
|
|
if (window.electronAPI) {
|
|
currentWorkspacePath = await window.electronAPI.fs.getWorkspacePath();
|
|
}
|
|
|
|
const newAssets = [...assets];
|
|
|
|
for (const file of files) {
|
|
const baseName = file.name.split('.')[0].replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
let uniqueId = baseName;
|
|
let counter = 1;
|
|
while (newAssets.some(a => a.id === uniqueId)) {
|
|
uniqueId = `${baseName}-${counter}`;
|
|
counter++;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
let type: 'image' | 'video' | 'audio' = 'image';
|
|
let subfolder = 'files/images';
|
|
if (file.type.startsWith('video/')) {
|
|
type = 'video';
|
|
subfolder = 'files/videos';
|
|
} else if (file.type.startsWith('audio/')) {
|
|
type = 'audio';
|
|
subfolder = 'files/audios';
|
|
}
|
|
|
|
if (currentWorkspacePath && company.id) {
|
|
formData.append('brandId', company.id);
|
|
formData.append('workspacePath', currentWorkspacePath);
|
|
formData.append('subfolder', subfolder);
|
|
}
|
|
|
|
const endpoint = (currentWorkspacePath && company.id) ? '/api/upload/brand' : '/api/upload';
|
|
const res = await fetch(endpoint, { method: 'POST', body: formData });
|
|
|
|
if (!res.ok) throw new Error('Upload failed');
|
|
const data = await res.json();
|
|
|
|
if (data.url) {
|
|
newAssets.push({
|
|
id: uniqueId,
|
|
type,
|
|
url: data.url,
|
|
path: data.path
|
|
});
|
|
}
|
|
}
|
|
|
|
handleDesignChange('brandAssets', newAssets);
|
|
} catch (err) {
|
|
console.error('Asset upload failed:', err);
|
|
alert('Ocurrió un error al subir los archivos.');
|
|
} finally {
|
|
setUploadingBrandAsset(false);
|
|
}
|
|
};
|
|
|
|
const handleDeleteAsset = (id: string) => {
|
|
if (confirm(`¿Estás seguro de que deseas eliminar el asset "${id}"?`)) {
|
|
handleDesignChange('brandAssets', assets.filter(a => a.id !== id));
|
|
}
|
|
};
|
|
|
|
const handleRenameAsset = (oldId: string, newId: string) => {
|
|
if (newId !== oldId && assets.some(a => a.id === newId)) {
|
|
alert('Este identificador ya existe.');
|
|
return;
|
|
}
|
|
|
|
const newAssets = assets.map(a => {
|
|
if (a.id === oldId) return { ...a, id: newId };
|
|
return a;
|
|
});
|
|
handleDesignChange('brandAssets', newAssets);
|
|
};
|
|
|
|
const onSelectUnified = (item: UnifiedMediaItem) => {
|
|
if (!onSelectAsset) return;
|
|
// Map UnifiedMediaItem back to BrandAsset if it's uploaded
|
|
if (item.source === 'uploaded') {
|
|
const asset = assets.find(a => a.id === item.id);
|
|
if (asset) onSelectAsset(asset);
|
|
} else {
|
|
// It's generated. We can construct a mock BrandAsset to show in preview
|
|
onSelectAsset({
|
|
id: item.name || item.id,
|
|
type: item.type,
|
|
path: item.path,
|
|
url: item.url,
|
|
date: item.date
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full space-y-6">
|
|
{/* Encabezado */}
|
|
<div className="flex items-center justify-between shrink-0">
|
|
<div>
|
|
<h3 className="text-sm font-bold text-white flex items-center gap-2 mb-1">
|
|
<Film size={16} className="text-violet-400" />
|
|
Librería Unificada
|
|
</h3>
|
|
<p className="text-xs text-neutral-500 leading-relaxed max-w-xl">
|
|
Gestiona tus archivos base de marca y tus renders generados en un solo lugar.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
<select
|
|
value={filterSource}
|
|
onChange={(e) => setFilterSource(e.target.value as SourceFilter)}
|
|
className="bg-neutral-900 border border-neutral-800 text-neutral-300 text-xs rounded-md px-2 py-1 outline-none"
|
|
>
|
|
<option value="all">Todos los orígenes</option>
|
|
<option value="uploaded">Assets Base (Subidos)</option>
|
|
<option value="generated">Renders Generados</option>
|
|
</select>
|
|
|
|
<select
|
|
value={filterType}
|
|
onChange={(e) => setFilterType(e.target.value as AssetFilter)}
|
|
className="bg-neutral-900 border border-neutral-800 text-neutral-300 text-xs rounded-md px-2 py-1 outline-none"
|
|
>
|
|
<option value="all">Todos los tipos</option>
|
|
<option value="image">Imágenes</option>
|
|
<option value="video">Videos</option>
|
|
<option value="audio">Audio</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Unified Media Library as Global Drop Zone */}
|
|
<div className="flex-1 overflow-hidden">
|
|
<UnifiedMediaLibrary
|
|
brandId={company.id}
|
|
brandAssets={assets}
|
|
refreshTrigger={refreshTrigger}
|
|
onDeleteAsset={handleDeleteAsset}
|
|
onRenameAsset={handleRenameAsset}
|
|
onSelect={onSelectUnified}
|
|
selectedPath={selectedAssetId}
|
|
filterType={filterType}
|
|
filterSource={filterSource}
|
|
draggable={false} // In this view, dragging is not needed for the timeline
|
|
onDropFiles={handleBrandAssetFiles}
|
|
isUploading={uploadingBrandAsset}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|