Update EntityDetailPanel to use 2-column grid layout
Add fullWidth field option to PanelField interface. Short fields render in a grid grid-cols-2 layout; fullWidth fields (address, notes) render below at full width. Add icons to People and Locations fields for consistency with other detail panels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
87a7a4ae32
commit
6fc134d113
@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { Plus, MapPin, Phone, Mail } from 'lucide-react';
|
||||
import { Plus, MapPin, Phone, Mail, Tag, AlignLeft } from 'lucide-react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import api, { getErrorMessage } from '@/lib/api';
|
||||
@ -240,11 +240,11 @@ export default function LocationsPage() {
|
||||
];
|
||||
|
||||
const panelFields: PanelField[] = [
|
||||
{ label: 'Address', key: 'address', copyable: true, icon: MapPin },
|
||||
{ label: 'Contact Number', key: 'contact_number', copyable: true, icon: Phone },
|
||||
{ label: 'Email', key: 'email', copyable: true, icon: Mail },
|
||||
{ label: 'Category', key: 'category' },
|
||||
{ label: 'Notes', key: 'notes', multiline: true },
|
||||
{ label: 'Category', key: 'category', icon: Tag },
|
||||
{ label: 'Address', key: 'address', copyable: true, icon: MapPin, fullWidth: true },
|
||||
{ label: 'Notes', key: 'notes', multiline: true, icon: AlignLeft, fullWidth: true },
|
||||
];
|
||||
|
||||
const renderPanel = () => (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { Plus, Users, Star, Cake, Phone, Mail, MapPin } from 'lucide-react';
|
||||
import { Plus, Users, Star, Cake, Phone, Mail, MapPin, Tag, Building2, Briefcase, AlignLeft } from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { format, parseISO, differenceInYears } from 'date-fns';
|
||||
@ -173,12 +173,12 @@ const panelFields: PanelField[] = [
|
||||
{ label: 'Mobile', key: 'mobile', copyable: true, icon: Phone },
|
||||
{ label: 'Phone', key: 'phone', copyable: true, icon: Phone },
|
||||
{ label: 'Email', key: 'email', copyable: true, icon: Mail },
|
||||
{ label: 'Address', key: 'address', copyable: true, icon: MapPin },
|
||||
{ label: 'Birthday', key: 'birthday_display' },
|
||||
{ label: 'Category', key: 'category' },
|
||||
{ label: 'Company', key: 'company' },
|
||||
{ label: 'Job Title', key: 'job_title' },
|
||||
{ label: 'Notes', key: 'notes', multiline: true },
|
||||
{ label: 'Birthday', key: 'birthday_display', icon: Cake },
|
||||
{ label: 'Category', key: 'category', icon: Tag },
|
||||
{ label: 'Company', key: 'company', icon: Building2 },
|
||||
{ label: 'Job Title', key: 'job_title', icon: Briefcase },
|
||||
{ label: 'Address', key: 'address', copyable: true, icon: MapPin, fullWidth: true },
|
||||
{ label: 'Notes', key: 'notes', multiline: true, icon: AlignLeft, fullWidth: true },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -11,6 +11,7 @@ export interface PanelField {
|
||||
copyable?: boolean;
|
||||
icon?: LucideIcon;
|
||||
multiline?: boolean;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
interface EntityDetailPanelProps<T> {
|
||||
@ -81,30 +82,55 @@ export function EntityDetailPanel<T>({
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-3">
|
||||
{fields.map((field) => {
|
||||
const value = getValue(item, field.key);
|
||||
if (!value) return null;
|
||||
{(() => {
|
||||
const gridFields = fields.filter((f) => !f.fullWidth && getValue(item, f.key));
|
||||
const fullWidthFields = fields.filter((f) => f.fullWidth && getValue(item, f.key));
|
||||
|
||||
return (
|
||||
<div key={field.key}>
|
||||
<>
|
||||
{gridFields.length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{gridFields.map((field) => {
|
||||
const value = getValue(item, field.key)!;
|
||||
return (
|
||||
<div key={field.key} className="space-y-1">
|
||||
<div className="flex items-center gap-1.5 text-[11px] text-muted-foreground uppercase tracking-wider">
|
||||
{field.icon && <field.icon className="h-3 w-3" />}
|
||||
{field.label}
|
||||
</div>
|
||||
{field.copyable ? (
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-[11px] uppercase tracking-wider text-muted-foreground">
|
||||
{field.label}
|
||||
</p>
|
||||
<CopyableField value={value} icon={field.icon} label={field.label} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-[11px] uppercase tracking-wider text-muted-foreground">
|
||||
{field.label}
|
||||
</p>
|
||||
<p className={`text-sm ${field.multiline ? 'whitespace-pre-wrap' : ''}`}>{value}</p>
|
||||
</div>
|
||||
<p className="text-sm">{value}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{fullWidthFields.map((field) => {
|
||||
const value = getValue(item, field.key)!;
|
||||
return (
|
||||
<div key={field.key} className="space-y-1">
|
||||
<div className="flex items-center gap-1.5 text-[11px] text-muted-foreground uppercase tracking-wider">
|
||||
{field.icon && <field.icon className="h-3 w-3" />}
|
||||
{field.label}
|
||||
</div>
|
||||
{field.copyable ? (
|
||||
<CopyableField value={value} icon={field.icon} label={field.label} />
|
||||
) : (
|
||||
<p className={`text-sm ${field.multiline ? 'whitespace-pre-wrap text-muted-foreground leading-relaxed' : ''}`}>
|
||||
{value}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-5 py-4 border-t border-border flex items-center justify-between">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user