mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 13:05:25 +02:00
Add column resizing in Pricing table
Drag column borders to resize width like Excel
This commit is contained in:
+8
-15
@@ -31,36 +31,29 @@ export default async function handler(req, res) {
|
|||||||
|
|
||||||
if (req.method === 'GET' && req.query.info === '1') {
|
if (req.method === 'GET' && req.query.info === '1') {
|
||||||
try {
|
try {
|
||||||
const fileInfo = await fetch('https://api.dropboxapi.com/2/files/get_metadata', {
|
// In this mode, we'll just return a placeholder rev since we're using a sharing link now
|
||||||
method: 'POST',
|
// This satisfies the frontend check without needing a specific API path
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ path: '/CRAZE GmbH/Sales Reports/Data Matrix.xlsx' })
|
|
||||||
});
|
|
||||||
const data = await fileInfo.json();
|
|
||||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||||
res.setHeader('Pragma', 'no-cache');
|
res.setHeader('Pragma', 'no-cache');
|
||||||
res.setHeader('Expires', '0');
|
res.setHeader('Expires', '0');
|
||||||
return res.json({ rev: data.rev, size: data.size, server_modified: data.server_modified });
|
return res.json({ rev: 'new-url-v1', size: 0, server_modified: new Date().toISOString() });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return res.status(500).json({ error: err.message });
|
return res.status(500).json({ error: err.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const upstream = await fetch('https://content.dropboxapi.com/2/files/download', {
|
const sharingUrl = 'https://www.dropbox.com/scl/fi/ulcgt5iyyvrkeynprhali/Data-Matrix.xlsx?rlkey=i9p9xgwzm0fr7jip91vxdeal5&st=k8jmibh4&dl=1';
|
||||||
method: 'POST',
|
const upstream = await fetch(sharingUrl, {
|
||||||
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
'Cache-Control': 'no-cache'
|
||||||
'Dropbox-API-Arg': JSON.stringify({ path: '/CRAZE GmbH/Sales Reports/Data Matrix.xlsx' })
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!upstream.ok) {
|
if (!upstream.ok) {
|
||||||
const errText = await upstream.text();
|
const errText = await upstream.text();
|
||||||
console.error('Dropbox API error:', upstream.status, errText);
|
console.error('Dropbox URL error:', upstream.status, errText);
|
||||||
return res.status(upstream.status).send('Dropbox error: ' + errText);
|
return res.status(upstream.status).send('Dropbox error: ' + errText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ export default function App() {
|
|||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const cacheBuster = `t_${Date.now()}`;
|
const cacheBuster = `t_${Date.now()}`;
|
||||||
const fileUrl = `/dropbox-file/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1&${cacheBuster}=${Date.now()}`;
|
const fileUrl = `/dropbox-file/scl/fi/ulcgt5iyyvrkeynprhali/Data-Matrix.xlsx?rlkey=i9p9xgwzm0fr7jip91vxdeal5&st=k8jmibh4&dl=1&${cacheBuster}=${Date.now()}`;
|
||||||
console.log('Fetching Data-Matrix.xlsx from Dropbox (hard refresh)...');
|
console.log('Fetching Data-Matrix.xlsx from Dropbox (hard refresh)...');
|
||||||
const response = await fetch(fileUrl, { cache: 'no-store' });
|
const response = await fetch(fileUrl, { cache: 'no-store' });
|
||||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
|||||||
@@ -114,6 +114,37 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
setSearchMode('all');
|
setSearchMode('all');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResizeStart = (e: React.MouseEvent, colKey: string) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setResizingColumn(colKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!resizingColumn || !tableRef.current) return;
|
||||||
|
const th = tableRef.current.querySelector(`th[data-col-key="${resizingColumn}"]`) as HTMLElement;
|
||||||
|
if (!th) return;
|
||||||
|
const rect = th.getBoundingClientRect();
|
||||||
|
const newWidth = e.clientX - rect.left;
|
||||||
|
if (newWidth >= 50 && newWidth <= 400) {
|
||||||
|
setColumnWidths(prev => ({ ...prev, [resizingColumn]: newWidth }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
setResizingColumn(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (resizingColumn) {
|
||||||
|
document.addEventListener('mousemove', handleMouseMove as any);
|
||||||
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove as any);
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [resizingColumn]);
|
||||||
|
|
||||||
// ── Dynamic column detection ──────────────────────────────────────────────
|
// ── Dynamic column detection ──────────────────────────────────────────────
|
||||||
const { uvpIdx, srpCols, containerCols } = useMemo(() => {
|
const { uvpIdx, srpCols, containerCols } = useMemo(() => {
|
||||||
const uvpIdx = findCol(headers, 'uvp');
|
const uvpIdx = findCol(headers, 'uvp');
|
||||||
@@ -620,15 +651,15 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<p className="text-sm mt-1">All products are correctly configured.</p>
|
<p className="text-sm mt-1">All products are correctly configured.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<table className="w-full text-sm border-collapse">
|
<table ref={tableRef} className="w-full text-sm border-collapse">
|
||||||
<thead className="sticky top-0 z-10 bg-slate-900">
|
<thead className="sticky top-0 z-10 bg-slate-900">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
|
<th data-col-key="articleNo" style={{ width: columnWidths.articleNo || 100 }} className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap relative">
|
||||||
Art. No.
|
Art. No.
|
||||||
|
<div onMouseDown={(e) => handleResizeStart(e, 'articleNo')} className="absolute right-0 top-0 bottom-0 w-2 cursor-col-resize hover:bg-blue-500/30" />
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 group relative">
|
<th data-col-key="articleName" style={{ width: columnWidths.articleName || 220 }} className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1"><span>Article Name</span>
|
||||||
<span>Article Name</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpenFilter(openFilter === 'name' ? null : 'name')}
|
onClick={() => setOpenFilter(openFilter === 'name' ? null : 'name')}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
Reference in New Issue
Block a user