Seth commited on
Commit
03b24dc
·
1 Parent(s): 164612a
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
- <EditableCell
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() === '') {