Seth commited on
Commit ·
03b24dc
1
Parent(s): 164612a
update
Browse files
frontend/src/components/workspace/EditableCell.jsx
CHANGED
|
@@ -66,6 +66,106 @@ export function EditableCell({
|
|
| 66 |
);
|
| 67 |
}
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
function toDateInputValue(iso) {
|
| 70 |
if (!iso) return '';
|
| 71 |
const d = new Date(iso);
|
|
|
|
| 66 |
);
|
| 67 |
}
|
| 68 |
|
| 69 |
+
function usdWholeString(value) {
|
| 70 |
+
if (value == null || value === '') return '';
|
| 71 |
+
const n = Math.round(Number(value));
|
| 72 |
+
return Number.isFinite(n) ? String(n) : '';
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
function formatUsdWholeDisplay(value) {
|
| 76 |
+
const s = usdWholeString(value);
|
| 77 |
+
if (s === '') return '';
|
| 78 |
+
return new Intl.NumberFormat('en-US', {
|
| 79 |
+
style: 'currency',
|
| 80 |
+
currency: 'USD',
|
| 81 |
+
maximumFractionDigits: 0,
|
| 82 |
+
minimumFractionDigits: 0,
|
| 83 |
+
}).format(Number(s));
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
function parseUsdWholeInput(raw) {
|
| 87 |
+
const cleaned = String(raw ?? '')
|
| 88 |
+
.replace(/[$,\s]/g, '')
|
| 89 |
+
.trim();
|
| 90 |
+
if (cleaned === '') return null;
|
| 91 |
+
const n = Math.round(Number(cleaned));
|
| 92 |
+
return Number.isFinite(n) ? n : NaN;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
/**
|
| 96 |
+
* USD whole dollars: shows $1,234 when blurred; plain digits while focused (no number spinners).
|
| 97 |
+
* onCommit receives '' to clear, or a digits string for the integer amount.
|
| 98 |
+
*/
|
| 99 |
+
export function EditableCurrencyCell({
|
| 100 |
+
value,
|
| 101 |
+
onCommit,
|
| 102 |
+
className = '',
|
| 103 |
+
inputClassName = '',
|
| 104 |
+
disabled = false,
|
| 105 |
+
}) {
|
| 106 |
+
const canonical = usdWholeString(value);
|
| 107 |
+
const [focused, setFocused] = useState(false);
|
| 108 |
+
const [local, setLocal] = useState(() =>
|
| 109 |
+
canonical ? formatUsdWholeDisplay(canonical) : ''
|
| 110 |
+
);
|
| 111 |
+
|
| 112 |
+
useEffect(() => {
|
| 113 |
+
if (focused) return;
|
| 114 |
+
setLocal(canonical ? formatUsdWholeDisplay(canonical) : '');
|
| 115 |
+
}, [canonical, focused]);
|
| 116 |
+
|
| 117 |
+
const commit = () => {
|
| 118 |
+
if (disabled) return;
|
| 119 |
+
const parsed = parseUsdWholeInput(local);
|
| 120 |
+
if (parsed === null) {
|
| 121 |
+
if (canonical !== '') onCommit('');
|
| 122 |
+
setLocal('');
|
| 123 |
+
setFocused(false);
|
| 124 |
+
return;
|
| 125 |
+
}
|
| 126 |
+
if (Number.isNaN(parsed)) {
|
| 127 |
+
setLocal(canonical ? formatUsdWholeDisplay(canonical) : '');
|
| 128 |
+
setFocused(false);
|
| 129 |
+
return;
|
| 130 |
+
}
|
| 131 |
+
const next = String(parsed);
|
| 132 |
+
if (next !== canonical) onCommit(next);
|
| 133 |
+
setLocal(formatUsdWholeDisplay(next));
|
| 134 |
+
setFocused(false);
|
| 135 |
+
};
|
| 136 |
+
|
| 137 |
+
const handleFocus = () => {
|
| 138 |
+
if (disabled) return;
|
| 139 |
+
setFocused(true);
|
| 140 |
+
setLocal(canonical);
|
| 141 |
+
};
|
| 142 |
+
|
| 143 |
+
const handleBlur = () => {
|
| 144 |
+
commit();
|
| 145 |
+
};
|
| 146 |
+
|
| 147 |
+
return (
|
| 148 |
+
<input
|
| 149 |
+
type="text"
|
| 150 |
+
inputMode="decimal"
|
| 151 |
+
autoComplete="off"
|
| 152 |
+
className={cn(baseInput, className, inputClassName)}
|
| 153 |
+
value={local}
|
| 154 |
+
onChange={(e) => setLocal(e.target.value)}
|
| 155 |
+
onFocus={handleFocus}
|
| 156 |
+
onBlur={handleBlur}
|
| 157 |
+
onKeyDown={(e) => {
|
| 158 |
+
e.stopPropagation();
|
| 159 |
+
if (e.key === 'Enter') {
|
| 160 |
+
e.preventDefault();
|
| 161 |
+
e.currentTarget.blur();
|
| 162 |
+
}
|
| 163 |
+
}}
|
| 164 |
+
disabled={disabled}
|
| 165 |
+
/>
|
| 166 |
+
);
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
function toDateInputValue(iso) {
|
| 170 |
if (!iso) return '';
|
| 171 |
const d = new Date(iso);
|
frontend/src/pages/Deals.jsx
CHANGED
|
@@ -11,7 +11,7 @@ import SlideOverPanel from '@/components/workspace/SlideOverPanel';
|
|
| 11 |
import CompanyDetailsEditor from '@/components/workspace/CompanyDetailsEditor';
|
| 12 |
import ContactIdentityEditor from '@/components/workspace/ContactIdentityEditor';
|
| 13 |
import { DealContactSearch, DealCompanySearch } from '@/components/workspace/DealLinkSearch';
|
| 14 |
-
import { EditableCell, EditableDateCell } from '@/components/workspace/EditableCell';
|
| 15 |
import { SearchableCountryPicker } from '@/components/workspace/SearchableCountryPicker';
|
| 16 |
import { cn } from '@/lib/utils';
|
| 17 |
import { OwnerAvatarCircle, ownerDisplayLabel } from '@/lib/ownerAvatar';
|
|
@@ -568,8 +568,7 @@ function DealRow({
|
|
| 568 |
className="px-3 py-2 align-top tabular-nums min-w-[10rem] w-40 max-w-[16rem]"
|
| 569 |
onClick={(e) => e.stopPropagation()}
|
| 570 |
>
|
| 571 |
-
<
|
| 572 |
-
type="number"
|
| 573 |
value={deal.deal_value != null ? String(deal.deal_value) : ''}
|
| 574 |
onCommit={(v) => {
|
| 575 |
if (v.trim() === '') {
|
|
|
|
| 11 |
import CompanyDetailsEditor from '@/components/workspace/CompanyDetailsEditor';
|
| 12 |
import ContactIdentityEditor from '@/components/workspace/ContactIdentityEditor';
|
| 13 |
import { DealContactSearch, DealCompanySearch } from '@/components/workspace/DealLinkSearch';
|
| 14 |
+
import { EditableCell, EditableCurrencyCell, EditableDateCell } from '@/components/workspace/EditableCell';
|
| 15 |
import { SearchableCountryPicker } from '@/components/workspace/SearchableCountryPicker';
|
| 16 |
import { cn } from '@/lib/utils';
|
| 17 |
import { OwnerAvatarCircle, ownerDisplayLabel } from '@/lib/ownerAvatar';
|
|
|
|
| 568 |
className="px-3 py-2 align-top tabular-nums min-w-[10rem] w-40 max-w-[16rem]"
|
| 569 |
onClick={(e) => e.stopPropagation()}
|
| 570 |
>
|
| 571 |
+
<EditableCurrencyCell
|
|
|
|
| 572 |
value={deal.deal_value != null ? String(deal.deal_value) : ''}
|
| 573 |
onCommit={(v) => {
|
| 574 |
if (v.trim() === '') {
|