Seth commited on
Commit
f0a2a7a
·
1 Parent(s): 2d06acb
Files changed (1) hide show
  1. frontend/src/pages/Contacts.jsx +76 -56
frontend/src/pages/Contacts.jsx CHANGED
@@ -1,5 +1,16 @@
1
  import React, { useEffect, useState } from 'react';
2
- import { Users, Mail, Building2, Briefcase, FileText, SlidersHorizontal, Loader2 } from 'lucide-react';
 
 
 
 
 
 
 
 
 
 
 
3
  import { Input } from '@/components/ui/input';
4
  import { Badge } from '@/components/ui/badge';
5
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
@@ -123,6 +134,43 @@ export default function Contacts() {
123
 
124
  const totalPages = Math.max(1, Math.ceil(total / Number(pageSize || 50)));
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  const filtersBlock = (
127
  <div className="space-y-3">
128
  <div className="flex items-center text-xs text-slate-500 gap-1.5 font-medium">
@@ -183,49 +231,6 @@ export default function Contacts() {
183
  )}
184
  </>
185
  )}
186
- <Select value={sortBy} onValueChange={setSortBy}>
187
- <SelectTrigger className="border-slate-200 bg-white">
188
- <SelectValue placeholder="Sort by" />
189
- </SelectTrigger>
190
- <SelectContent>
191
- <SelectItem value="created_at">Created At</SelectItem>
192
- <SelectItem value="first_name">First Name</SelectItem>
193
- <SelectItem value="last_name">Last Name</SelectItem>
194
- <SelectItem value="email">Email</SelectItem>
195
- <SelectItem value="company">Company</SelectItem>
196
- <SelectItem value="title">Title</SelectItem>
197
- {fields.map((field) => (
198
- <SelectItem key={`sort-${field}`} value={field}>
199
- {field}
200
- </SelectItem>
201
- ))}
202
- </SelectContent>
203
- </Select>
204
- <Select value={sortDir} onValueChange={setSortDir}>
205
- <SelectTrigger className="border-slate-200 bg-white">
206
- <SelectValue />
207
- </SelectTrigger>
208
- <SelectContent>
209
- <SelectItem value="asc">Ascending</SelectItem>
210
- <SelectItem value="desc">Descending</SelectItem>
211
- </SelectContent>
212
- </Select>
213
- <Select
214
- value={pageSize}
215
- onValueChange={(v) => {
216
- setPageSize(v);
217
- setPage(1);
218
- }}
219
- >
220
- <SelectTrigger className="border-slate-200 bg-white">
221
- <SelectValue />
222
- </SelectTrigger>
223
- <SelectContent>
224
- <SelectItem value="25">25 / page</SelectItem>
225
- <SelectItem value="50">50 / page</SelectItem>
226
- <SelectItem value="100">100 / page</SelectItem>
227
- </SelectContent>
228
- </Select>
229
  </div>
230
  </div>
231
  );
@@ -236,10 +241,6 @@ export default function Contacts() {
236
  subtitle="Apollo CSV contacts stored in SQLite with full field payload."
237
  >
238
  <MainTableWorkspace
239
- tabs={[
240
- { label: 'Main table', active: true },
241
- { label: 'By company', active: false, disabled: true },
242
- ]}
243
  primaryAction={{ label: 'New contact', to: '/' }}
244
  search={{
245
  value: searchQuery,
@@ -272,11 +273,11 @@ export default function Contacts() {
272
  <>
273
  <table className="w-full text-sm min-w-[720px]">
274
  <thead>
275
- <tr className="bg-slate-50 text-left text-slate-600 border-b border-slate-200">
276
- <th className="px-3 py-2 font-medium">Name</th>
277
- <th className="px-3 py-2 font-medium">Email</th>
278
- <th className="px-3 py-2 font-medium">Company</th>
279
- <th className="px-3 py-2 font-medium">Title</th>
280
  </tr>
281
  </thead>
282
  <tbody>
@@ -315,9 +316,28 @@ export default function Contacts() {
315
  })}
316
  </tbody>
317
  </table>
318
- <div className="flex items-center justify-between px-4 py-3 border-t border-slate-100 bg-slate-50/50">
319
- <div className="text-xs text-slate-500">
320
- Page {page} of {totalPages}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  </div>
322
  <div className="flex items-center gap-2">
323
  <Button
 
1
  import React, { useEffect, useState } from 'react';
2
+ import {
3
+ Users,
4
+ Mail,
5
+ Building2,
6
+ Briefcase,
7
+ FileText,
8
+ SlidersHorizontal,
9
+ Loader2,
10
+ ArrowUp,
11
+ ArrowDown,
12
+ ArrowUpDown,
13
+ } from 'lucide-react';
14
  import { Input } from '@/components/ui/input';
15
  import { Badge } from '@/components/ui/badge';
16
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
 
134
 
135
  const totalPages = Math.max(1, Math.ceil(total / Number(pageSize || 50)));
136
 
137
+ const toggleSort = (field) => {
138
+ if (sortBy === field) {
139
+ setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
140
+ } else {
141
+ setSortBy(field);
142
+ setSortDir('asc');
143
+ }
144
+ setPage(1);
145
+ };
146
+
147
+ const SortHeader = ({ field, label, className = '' }) => {
148
+ const active = sortBy === field;
149
+ return (
150
+ <th className={`px-3 py-2 font-medium ${className}`}>
151
+ <button
152
+ type="button"
153
+ className="inline-flex items-center gap-1.5 text-left text-slate-600 hover:text-violet-700 transition-colors group"
154
+ onClick={() => toggleSort(field)}
155
+ >
156
+ <span>{label}</span>
157
+ {active ? (
158
+ sortDir === 'asc' ? (
159
+ <ArrowUp className="h-3.5 w-3.5 text-violet-600 shrink-0" aria-hidden />
160
+ ) : (
161
+ <ArrowDown className="h-3.5 w-3.5 text-violet-600 shrink-0" aria-hidden />
162
+ )
163
+ ) : (
164
+ <ArrowUpDown
165
+ className="h-3.5 w-3.5 text-slate-300 group-hover:text-slate-400 shrink-0"
166
+ aria-hidden
167
+ />
168
+ )}
169
+ </button>
170
+ </th>
171
+ );
172
+ };
173
+
174
  const filtersBlock = (
175
  <div className="space-y-3">
176
  <div className="flex items-center text-xs text-slate-500 gap-1.5 font-medium">
 
231
  )}
232
  </>
233
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  </div>
235
  </div>
236
  );
 
241
  subtitle="Apollo CSV contacts stored in SQLite with full field payload."
242
  >
243
  <MainTableWorkspace
 
 
 
 
244
  primaryAction={{ label: 'New contact', to: '/' }}
245
  search={{
246
  value: searchQuery,
 
273
  <>
274
  <table className="w-full text-sm min-w-[720px]">
275
  <thead>
276
+ <tr className="bg-slate-50 text-left border-b border-slate-200">
277
+ <SortHeader field="first_name" label="Name" />
278
+ <SortHeader field="email" label="Email" />
279
+ <SortHeader field="company" label="Company" />
280
+ <SortHeader field="title" label="Title" />
281
  </tr>
282
  </thead>
283
  <tbody>
 
316
  })}
317
  </tbody>
318
  </table>
319
+ <div className="flex flex-wrap items-center justify-between gap-3 px-4 py-3 border-t border-slate-100 bg-slate-50/50">
320
+ <div className="flex flex-wrap items-center gap-3 text-xs text-slate-600">
321
+ <span className="text-slate-500">Rows per page</span>
322
+ <Select
323
+ value={pageSize}
324
+ onValueChange={(v) => {
325
+ setPageSize(v);
326
+ setPage(1);
327
+ }}
328
+ >
329
+ <SelectTrigger className="h-8 w-[88px] border-slate-200 bg-white text-xs">
330
+ <SelectValue />
331
+ </SelectTrigger>
332
+ <SelectContent>
333
+ <SelectItem value="25">25</SelectItem>
334
+ <SelectItem value="50">50</SelectItem>
335
+ <SelectItem value="100">100</SelectItem>
336
+ </SelectContent>
337
+ </Select>
338
+ <span className="text-slate-500">
339
+ Page {page} of {totalPages}
340
+ </span>
341
  </div>
342
  <div className="flex items-center gap-2">
343
  <Button