Дизайн таблиц
This commit is contained in:
@@ -12,7 +12,7 @@ import { useMultiSheetEngine } from '../../hooks/useMultiSheetEngine'
|
|||||||
|
|
||||||
const ROW_COUNT = 90
|
const ROW_COUNT = 90
|
||||||
const COL_COUNT = 20
|
const COL_COUNT = 20
|
||||||
const ROW_HEIGHT = 32
|
const ROW_HEIGHT = 28
|
||||||
|
|
||||||
const SHEET_NAMES = {
|
const SHEET_NAMES = {
|
||||||
report: 'L',
|
report: 'L',
|
||||||
@@ -66,9 +66,9 @@ const Cell = memo(
|
|||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
activeCellRef,
|
activeCellRef,
|
||||||
}: CellProps) => {
|
}: CellProps) => {
|
||||||
const className = `border border-gray-200 ${
|
const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${
|
||||||
isSelected ? 'bg-blue-100' : ''
|
isModified ? 'bg-yellow-50' : ''
|
||||||
} ${isModified ? 'bg-yellow-50' : ''}`
|
}`
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
minWidth: `${columnWidth}px`,
|
minWidth: `${columnWidth}px`,
|
||||||
@@ -112,12 +112,16 @@ const Cell = memo(
|
|||||||
stopEditing(false)
|
stopEditing(false)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="absolute left-0 top-0 z-20 box-border h-full w-full border-2 border-blue-500 p-2"
|
className="absolute left-0 top-0 z-20 box-border h-full w-full border-2 border-primary px-2 py-1"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="relative h-full w-full truncate p-2"
|
className="relative h-full w-full truncate px-2 py-1"
|
||||||
style={isSelected ? { boxShadow: 'inset 0 0 0 2px #3b82f6' } : {}}
|
style={
|
||||||
|
isSelected
|
||||||
|
? { boxShadow: 'inset 0 0 0 2px hsl(var(--primary))' }
|
||||||
|
: {}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{displayValue}
|
{displayValue}
|
||||||
</div>
|
</div>
|
||||||
@@ -157,43 +161,74 @@ const FormulaBar = memo(
|
|||||||
}: FormulaBarProps) => {
|
}: FormulaBarProps) => {
|
||||||
if (activeSheet !== sheetType) {
|
if (activeSheet !== sheetType) {
|
||||||
return (
|
return (
|
||||||
<div className="flex-shrink-0 border-b border-gray-200 bg-white p-2">
|
<div className="flex-shrink-0 border-b bg-background px-3 py-1">
|
||||||
<div className="flex h-[34px] items-center" />
|
<div className="flex h-[28px] items-center" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-shrink-0 border-b border-gray-200 bg-white p-2">
|
<div className="flex-shrink-0 border-b bg-background px-3 py-1">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="min-w-[60px] text-[1rem] text-gray-600">
|
<div className="flex min-w-[50px] items-center justify-center rounded-md border bg-muted px-2 py-1">
|
||||||
<b>
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
{selectedCell
|
{selectedCell
|
||||||
? `${getColumnLabel(selectedCell.col)}${selectedCell.row + 1}`
|
? `${getColumnLabel(selectedCell.col)}${selectedCell.row + 1}`
|
||||||
: ''}
|
: 'A1'}
|
||||||
</b>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{isCalculating && (
|
<div className="flex flex-1 items-center space-x-2">
|
||||||
<div className="animate-pulse text-xs text-blue-600">
|
<div className="flex items-center text-muted-foreground">
|
||||||
Вычисляется...
|
<svg
|
||||||
|
className="h-3.5 w-3.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<div className="flex-1">
|
|
||||||
<input
|
<input
|
||||||
ref={formulaBarRef}
|
ref={formulaBarRef}
|
||||||
type="text"
|
type="text"
|
||||||
className={`w-full rounded border px-2 py-1 text-sm focus:outline-none ${
|
className={`flex-1 rounded-md border border-input bg-background px-2 py-1 text-sm ring-offset-background transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${
|
||||||
isEditing
|
isEditing ? 'ring-2 ring-ring ring-offset-2' : ''
|
||||||
? 'border-blue-400 focus:ring-2 focus:ring-blue-300'
|
} ${isCalculating ? 'animate-pulse' : ''}`}
|
||||||
: 'border-gray-300 focus:ring-2 focus:ring-blue-500'
|
placeholder="Введите формулу или значение..."
|
||||||
}`}
|
|
||||||
placeholder="Введите формулу..."
|
|
||||||
value={formulaBarValue}
|
value={formulaBarValue}
|
||||||
onChange={(e) => onValueChange(e.target.value)}
|
onChange={(e) => onValueChange(e.target.value)}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
|
{isCalculating && (
|
||||||
|
<div className="flex items-center text-muted-foreground">
|
||||||
|
<svg
|
||||||
|
className="h-3.5 w-3.5 animate-spin"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
className="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="4"
|
||||||
|
></circle>
|
||||||
|
<path
|
||||||
|
className="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,7 +345,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
}
|
}
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
setEditingValue('') // Очищаем временное значение
|
setEditingValue('') // Очищаем временное значение
|
||||||
containerRefs.current[activeSheet]?.focus()
|
containerRefs.current[activeSheet]?.focus({ preventScroll: true })
|
||||||
},
|
},
|
||||||
[activeSheet, selectedCell, editingValue, multiSheetEngine],
|
[activeSheet, selectedCell, editingValue, multiSheetEngine],
|
||||||
)
|
)
|
||||||
@@ -404,7 +439,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
stopEditing(true)
|
stopEditing(true)
|
||||||
}
|
}
|
||||||
setSelectedCell({ row, col })
|
setSelectedCell({ row, col })
|
||||||
containerRefs.current[activeSheet]?.focus()
|
containerRefs.current[activeSheet]?.focus({ preventScroll: true })
|
||||||
},
|
},
|
||||||
[isEditing, stopEditing, activeSheet],
|
[isEditing, stopEditing, activeSheet],
|
||||||
)
|
)
|
||||||
@@ -459,10 +494,14 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
stopEditing(true)
|
stopEditing(true)
|
||||||
moveSelection(1, 0)
|
moveSelection(1, 0)
|
||||||
|
// Предотвращаем прокрутку страницы
|
||||||
|
setTimeout(() => {
|
||||||
|
containerRefs.current[activeSheet]?.focus({ preventScroll: true })
|
||||||
|
}, 0)
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
stopEditing(false)
|
stopEditing(false)
|
||||||
containerRefs.current[activeSheet]?.focus()
|
containerRefs.current[activeSheet]?.focus({ preventScroll: true })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[stopEditing, moveSelection, activeSheet],
|
[stopEditing, moveSelection, activeSheet],
|
||||||
@@ -476,22 +515,30 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={(el) => (containerRefs.current[sheetType] = el)}
|
ref={(el) => (containerRefs.current[sheetType] = el)}
|
||||||
className="overflow-auto rounded-lg border border-gray-300 bg-white shadow-md"
|
className="flex h-full flex-col overflow-hidden rounded-lg border bg-card shadow-sm"
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'relative',
|
|
||||||
}}
|
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
|
<div className="flex-shrink-0 border-b bg-muted/50 px-3 py-1">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div
|
||||||
|
className={`h-2 w-2 rounded-full ${
|
||||||
|
sheetType === 'report' ? 'bg-primary' : 'bg-emerald-500'
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<h3 className="text-xs font-medium text-muted-foreground">
|
||||||
|
{sheetType === 'report' ? 'Отчет' : 'Расчеты'}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-auto">
|
||||||
<table
|
<table
|
||||||
className="w-full border-collapse"
|
className="w-full border-collapse"
|
||||||
style={{ tableLayout: 'fixed' }}
|
style={{ tableLayout: 'fixed' }}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="h-8 w-12 border border-gray-300 bg-gray-100 text-xs font-medium text-gray-600"></th>
|
<th className="h-7 w-10 border border-border bg-muted text-xs font-medium text-muted-foreground"></th>
|
||||||
{Array.from({ length: COL_COUNT }, (_, i) => {
|
{Array.from({ length: COL_COUNT }, (_, i) => {
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -514,12 +561,12 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
return (
|
return (
|
||||||
<th
|
<th
|
||||||
key={i}
|
key={i}
|
||||||
className="relative h-8 border border-gray-300 bg-gray-100 text-center text-xs font-medium text-gray-600"
|
className="relative h-7 border border-border bg-muted text-center text-xs font-medium text-muted-foreground"
|
||||||
style={{ width: widths[i] }}
|
style={{ width: widths[i] }}
|
||||||
>
|
>
|
||||||
{getColumnLabel(i)}
|
{getColumnLabel(i)}
|
||||||
<div
|
<div
|
||||||
className="absolute right-0 top-0 h-full w-1 cursor-col-resize hover:bg-blue-500 hover:bg-opacity-50"
|
className="absolute right-0 top-0 h-full w-1 cursor-col-resize hover:bg-primary hover:bg-opacity-50"
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
@@ -530,7 +577,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => (
|
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => (
|
||||||
<tr key={rowIndex}>
|
<tr key={rowIndex}>
|
||||||
<td className="h-8 w-12 border border-gray-300 bg-gray-100 text-center text-xs font-medium text-gray-600">
|
<td className="h-7 w-10 border border-border bg-muted text-center text-xs font-medium text-muted-foreground">
|
||||||
{rowIndex + 1}
|
{rowIndex + 1}
|
||||||
</td>
|
</td>
|
||||||
{Array.from({ length: COL_COUNT }).map((_, colIndex) => {
|
{Array.from({ length: COL_COUNT }).map((_, colIndex) => {
|
||||||
@@ -591,6 +638,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,22 +671,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col space-y-4 bg-gray-100 p-4">
|
<div className="flex h-screen flex-col bg-background">
|
||||||
{/* Двойная панель */}
|
|
||||||
<div className="flex flex-grow space-x-4 overflow-hidden">
|
|
||||||
{/* Левая панель - Отчет */}
|
|
||||||
<div className="flex w-1/2 flex-col">
|
|
||||||
<h2 className="mb-2 text-lg font-semibold">Отчет</h2>
|
|
||||||
{memoizedReportSheet}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Правая панель - Вычисления */}
|
|
||||||
<div className="flex w-1/2 flex-col">
|
|
||||||
<h2 className="mb-2 text-lg font-semibold">Расчеты</h2>
|
|
||||||
{memoizedCalculationsSheet}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<FormulaBar
|
<FormulaBar
|
||||||
sheetType={activeSheet}
|
sheetType={activeSheet}
|
||||||
@@ -654,6 +687,11 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
formulaBarRef={(el) => (formulaBarRefs.current[activeSheet] = el)}
|
formulaBarRef={(el) => (formulaBarRefs.current[activeSheet] = el)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Двойная панель */}
|
||||||
|
<div className="flex flex-1 gap-1 overflow-hidden p-1">
|
||||||
|
<div className="min-w-0 flex-1">{memoizedReportSheet}</div>
|
||||||
|
<div className="min-w-0 flex-1">{memoizedCalculationsSheet}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user