Merge all role-dispatcher pages into single files; add FileBrowser with file-type icons
- DashboardPage, Projects, RequestsPage, ProjectDetailPage, RequestDetail: each now handles team/external/client in one file via role flags — removed 10 old role-specific sub-files - Layout: client Company nav link goes directly to /company/:id when user has a single company - FileBrowser: replace emoji icons with colored extension-text badges (square); folder icon stays 📁; Adobe/Figma/design-tool colors for design files - CompaniesPage: merged team Companies + client company routing (single-company redirect, multi-company list) - FileSharing: integrated FileBrowser component - Removed: seafile API + lib, old ServerStatus, TaskDetail, role-split page files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { supabase } from './supabase';
|
||||
|
||||
async function fbCall(method, action, body = null) {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token) return;
|
||||
|
||||
await fetch(`/api/filebrowser?action=${action}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Create /Clients/{name}/ folder. Silently fails if already exists.
|
||||
export async function createClientFolder(companyName) {
|
||||
if (!companyName) return;
|
||||
// Ensure /Clients dir exists first
|
||||
await fbCall('POST', 'mkdir', { path: '/', name: 'Clients' });
|
||||
await fbCall('POST', 'mkdir', { path: '/Clients', name: companyName });
|
||||
}
|
||||
|
||||
// Rename /Clients/{oldName} → /Clients/{newName}
|
||||
export async function renameClientFolder(oldName, newName) {
|
||||
if (!oldName || !newName || oldName === newName) return;
|
||||
await fbCall('POST', 'rename', { path: `/Clients/${oldName}`, name: newName });
|
||||
}
|
||||
|
||||
// Upload files to Clients/{company}/Projects/{project}/{task}/Request Info/ in FileBrowser.
|
||||
// Best-effort — call with .catch(() => {}) so failures don't block submission.
|
||||
export async function uploadFilesToRequestInfo(files, projectName, taskTitle, versionNumber = 0) {
|
||||
if (!files?.length || !projectName || !taskTitle) return;
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token) return;
|
||||
const authHeader = `Bearer ${session.access_token}`;
|
||||
|
||||
const revFolder = `R${String(versionNumber).padStart(2, '0')}`;
|
||||
|
||||
// Ensure folder hierarchy exists (mkdir is idempotent)
|
||||
const segments = [
|
||||
{ path: '/', name: 'Projects' },
|
||||
{ path: '/Projects', name: projectName },
|
||||
{ path: `/Projects/${projectName}`, name: taskTitle },
|
||||
{ path: `/Projects/${projectName}/${taskTitle}`, name: 'Request Info' },
|
||||
{ path: `/Projects/${projectName}/${taskTitle}/Request Info`, name: revFolder },
|
||||
];
|
||||
for (const seg of segments) {
|
||||
await fetch('/api/filebrowser?action=mkdir', {
|
||||
method: 'POST',
|
||||
headers: { Authorization: authHeader, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: seg.path, name: seg.name }),
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Get upload token for R## folder
|
||||
const virtualPath = `/Projects/${projectName}/${taskTitle}/Request Info/${revFolder}`;
|
||||
const tokenRes = await fetch('/api/filebrowser?action=upload-token', {
|
||||
method: 'POST',
|
||||
headers: { Authorization: authHeader, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: virtualPath }),
|
||||
}).catch(() => null);
|
||||
if (!tokenRes?.ok) return;
|
||||
|
||||
const { token, url, fbPath } = await tokenRes.json();
|
||||
if (!token || !url || !fbPath) return;
|
||||
|
||||
for (const file of files) {
|
||||
await fetch(`${url}/api/resources?source=files&path=${encodeURIComponent(`${fbPath}/${file.name}`)}&override=true`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${token}`, 'Content-Type': file.type || 'application/octet-stream' },
|
||||
body: file,
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// Create missing /Clients/{name}/ folders for all companies. Run on create/rename only.
|
||||
export async function backfillClientFolders() {
|
||||
const { data } = await supabase.from('companies').select('name');
|
||||
if (!data?.length) return;
|
||||
await fbCall('POST', 'mkdir', { path: '/', name: 'Clients' });
|
||||
for (const company of data) {
|
||||
if (company.name) await fbCall('POST', 'mkdir', { path: '/Clients', name: company.name });
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { supabase } from './supabase';
|
||||
|
||||
export async function syncSeafileFolders() {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token) return { skipped: true };
|
||||
|
||||
const response = await fetch('/api/seafile?action=sync-folders', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to sync Seafile folders.');
|
||||
return data;
|
||||
}
|
||||
Reference in New Issue
Block a user