mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-04 18:22:05 +00:00
Clamp column resize so neither column goes below 50px
- Limit drag diff so both the dragged and neighbor column stay >= 50px - Prevents columns from collapsing or going negative
This commit is contained in:
parent
33431ae449
commit
168383b0ee
2 changed files with 163 additions and 70 deletions
|
|
@ -1,82 +1,124 @@
|
|||
import React from "react";
|
||||
import React, { useState, useCallback, useRef } from "react";
|
||||
|
||||
const SharedTable = ({
|
||||
rows = [],
|
||||
columns = [],
|
||||
onEdit,
|
||||
onDelete,
|
||||
page,
|
||||
setPage,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
const SharedTable = ({
|
||||
rows = [],
|
||||
columns = [],
|
||||
onEdit,
|
||||
onDelete,
|
||||
page,
|
||||
setPage,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
totalCount,
|
||||
loading,
|
||||
error
|
||||
error
|
||||
}) => {
|
||||
const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
|
||||
const [colWidths, setColWidths] = useState(null);
|
||||
const gridRef = useRef(null);
|
||||
|
||||
const snapshotWidths = useCallback(() => {
|
||||
if (!gridRef.current) return null;
|
||||
const headerCells = gridRef.current.querySelectorAll("[data-col]");
|
||||
const widths = {};
|
||||
headerCells.forEach((cell) => {
|
||||
widths[cell.dataset.col] = cell.getBoundingClientRect().width;
|
||||
});
|
||||
return widths;
|
||||
}, []);
|
||||
|
||||
const handleMouseDown = useCallback((e, colKey) => {
|
||||
e.preventDefault();
|
||||
const startX = e.clientX;
|
||||
|
||||
const currentWidths = snapshotWidths();
|
||||
if (!currentWidths) return;
|
||||
setColWidths(currentWidths);
|
||||
|
||||
const colIndex = columns.findIndex((c) => c.key === colKey);
|
||||
const nextColKey = colIndex < columns.length - 1 ? columns[colIndex + 1].key : null;
|
||||
const startWidth = currentWidths[colKey] || 100;
|
||||
const nextStartWidth = nextColKey ? (currentWidths[nextColKey] || 100) : 0;
|
||||
|
||||
const onMove = (moveEvent) => {
|
||||
let diff = moveEvent.clientX - startX;
|
||||
const minCol = 50;
|
||||
if (startWidth + diff < minCol) diff = minCol - startWidth;
|
||||
if (nextColKey && nextStartWidth - diff < minCol) diff = nextStartWidth - minCol;
|
||||
setColWidths((prev) => {
|
||||
const updated = { ...prev, [colKey]: startWidth + diff };
|
||||
if (nextColKey) updated[nextColKey] = nextStartWidth - diff;
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
const onUp = () => {
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
};
|
||||
|
||||
document.body.style.cursor = "col-resize";
|
||||
document.body.style.userSelect = "none";
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
}, [snapshotWidths]);
|
||||
|
||||
if (loading) return <div className="loading">Loading...</div>;
|
||||
if (error) return <div className="error">{error}</div>;
|
||||
|
||||
const gridTemplate = colWidths
|
||||
? ["40px", ...columns.map((col) => `${colWidths[col.key] || 100}px`), "80px"].join(" ")
|
||||
: ["40px", ...columns.map(() => "1fr"), "80px"].join(" ");
|
||||
|
||||
return (
|
||||
<div className="tableContainer">
|
||||
<div className="tableResponsive">
|
||||
<table className="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: "1%" }}>
|
||||
<div className="gridTable" ref={gridRef} style={{ gridTemplateColumns: gridTemplate }}>
|
||||
<div className="gridCell gridHeader">
|
||||
<input type="checkbox" />
|
||||
</div>
|
||||
{columns.map((col) => (
|
||||
<div key={col.key} data-col={col.key} className="gridCell gridHeader">
|
||||
{col.label}
|
||||
<span
|
||||
className="colResizeHandle"
|
||||
onMouseDown={(e) => handleMouseDown(e, col.key)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="gridCell gridHeader">Actions</div>
|
||||
|
||||
{rows.map((row, idx) => (
|
||||
<React.Fragment key={row.id || idx}>
|
||||
<div className="gridCell gridBody">
|
||||
<input type="checkbox" />
|
||||
</th>
|
||||
</div>
|
||||
{columns.map((col) => (
|
||||
<th key={col.key} style={col.style}>
|
||||
{col.label}
|
||||
</th>
|
||||
<div key={col.key} className="gridCell gridBody">
|
||||
{col.render ? col.render(row[col.key], row) : row[col.key]}
|
||||
</div>
|
||||
))}
|
||||
<th style={{ width: "1%" }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, idx) => (
|
||||
<tr key={row.id || idx}>
|
||||
<td>
|
||||
<input type="checkbox" />
|
||||
</td>
|
||||
{columns.map((col) => (
|
||||
<td key={col.key}>
|
||||
{col.render
|
||||
? col.render(row[col.key], row)
|
||||
: row[col.key]}
|
||||
</td>
|
||||
))}
|
||||
<td>
|
||||
<div className="actionButtons">
|
||||
{onEdit && (
|
||||
<button
|
||||
className="btnIcon edit"
|
||||
title="Edit"
|
||||
onClick={() => onEdit(row)}
|
||||
>
|
||||
✎
|
||||
</button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<button
|
||||
className="btnIcon delete"
|
||||
title="Delete"
|
||||
onClick={() => onDelete(row)}
|
||||
>
|
||||
🗑
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="gridCell gridBody">
|
||||
<div className="actionButtons">
|
||||
{onEdit && (
|
||||
<button className="btnIcon edit" title="Edit" onClick={() => onEdit(row)}>
|
||||
✎
|
||||
</button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<button className="btnIcon delete" title="Delete" onClick={() => onDelete(row)}>
|
||||
🗑
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="paginationContainer">
|
||||
<div className="paginationLeft">
|
||||
<label>
|
||||
|
|
@ -98,17 +140,11 @@ const SharedTable = ({
|
|||
Showing {Math.min(page * pageSize, totalCount)} of {totalCount}
|
||||
</span>
|
||||
<div className="pagination">
|
||||
<button
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={page === 1}
|
||||
>
|
||||
<button onClick={() => setPage(page - 1)} disabled={page === 1}>
|
||||
← Previous
|
||||
</button>
|
||||
<span>Page {page} of {totalPages}</span>
|
||||
<button
|
||||
onClick={() => setPage(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
>
|
||||
<button onClick={() => setPage(page + 1)} disabled={page >= totalPages}>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
57
src/site.css
57
src/site.css
|
|
@ -137,6 +137,63 @@ body {
|
|||
padding: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gridTable {
|
||||
display: grid;
|
||||
font-size: 14px;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.gridCell {
|
||||
padding: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gridHeader {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
background: #f9f9f9;
|
||||
border-bottom: 2px solid #ddd;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gridBody {
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gridBody:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.gridCell a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gridCell a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.colResizeHandle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.colResizeHandle:hover {
|
||||
background: #007bff;
|
||||
}
|
||||
|
||||
.dataTable tbody tr:hover {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue