Seth commited on
Commit ·
f0a2a7a
1
Parent(s): 2d06acb
update
Browse files- 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 {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 276 |
-
<
|
| 277 |
-
<
|
| 278 |
-
<
|
| 279 |
-
<
|
| 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-
|
| 320 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|