// src/views/portfolio/PortfolioView.tsx
import React, { useState } from "react";
import {
ObjectSearchComponent,
useExecuteRequestCallback,
useExecuteRequest,
useDeleteAction,
useRouterContext,
useMultiEventSubscriber,
usePluggableView,
useSearchParams,
type OSCColumn,
type RowData,
type Filters,
type Sort,
type DynamicFormFieldProps,
type ViewsConfig,
type View,
type CustomDetailsPanelProps,
type ExecuteConfig,
type GenerateURLOptions,
type DeleteActionParams
} from "uxp/components";
import { LocationServices } from "../../services";
import LocationFormComponent from "../../components/location-form/LocationFormComponent";
import LocationDetailsPanel from "../../components/location-details-panel/LocationDetailsPanel";
interface SearchParams {
selectedKey?: string;
view?: string;
}
interface LocationData extends RowData {
Key: string;
Name: string;
LocationType: string;
Street: string;
City: string;
Status: string;
}
const PortfolioView = () => {
const { navigate, generateURL, location } = useRouterContext();
const searchParams = useSearchParams<SearchParams>();
const [showForm, setShowForm] = useState(false);
const [editingLocation, setEditingLocation] = useState<LocationData | null>(null);
const [refreshKey, setRefreshKey] = useState(0);
const selectedKey = searchParams.selectedKey;
// Load location types for filters
const [locationTypes] = useExecuteRequest<string[]>(
LocationServices.getLocationTypes() as ExecuteConfig,
{},
[]
);
// Delete action
const deleteAction = useDeleteAction();
// Data fetching
const executeGetAll = useExecuteRequestCallback(
LocationServices.getAll()
);
const getAll = async (
page: number,
pageSize: number,
query?: string,
filters?: Filters,
sort?: Sort
): Promise<{ items: RowData[] }> => {
const params = {
page,
pageSize,
q: query,
...filters?.filters,
...sort
};
const { data } = await executeGetAll(params);
return { items: data || [] };
};
// Real-time updates
useMultiEventSubscriber([
{
event: 'location-created',
handler: () => setRefreshKey(prev => prev + 1)
},
{
event: 'location-updated',
handler: () => setRefreshKey(prev => prev + 1)
}
]);
// Delete handler
const handleDelete = async (item: LocationData) => {
const params: DeleteActionParams = {
model: 'Location.Location',
key: item.Key,
canHide: true,
onSuccess: () => {
setRefreshKey(prev => prev + 1);
}
};
await deleteAction.delete(params);
};
// Columns
const columns: OSCColumn[] = [
{
id: 'Name',
label: 'Name',
isSortable: true,
renderColumn: (item: LocationData) => {
const urlOptions: GenerateURLOptions = {
searchParams: { selectedKey: item.Key }
};
return (
<a
href={generateURL(urlOptions)}
onClick={(e) => {
e.preventDefault();
if (navigate) {
navigate(generateURL(urlOptions));
}
}}
>
{item.Name}
</a>
);
}
},
{
id: 'LocationType',
label: 'Type',
isSortable: true
},
{
id: 'Address',
label: 'Address',
renderColumn: (item: LocationData) => (
<span>{item.Street}, {item.City}</span>
)
},
{
id: 'Status',
label: 'Status',
renderColumn: (item: LocationData) => (
<span className={`status-badge status-${item.Status.toLowerCase()}`}>
{item.Status}
</span>
)
},
{
id: 'actions',
label: 'Actions',
renderColumn: (item: LocationData) => (
<div className="action-buttons">
<button onClick={() => {
setEditingLocation(item);
setShowForm(true);
}}>
Edit
</button>
<button onClick={() => handleDelete(item)}>
{item.Status === 'Active' ? 'Delete' : 'Make Visible'}
</button>
</div>
)
}
];
// Filters
const filterFields: DynamicFormFieldProps[] = [
{
name: 'LocationType',
label: 'Location Type',
type: 'select',
value: '',
options: locationTypes?.map((type: string) => ({
label: type,
value: type
})) || []
},
{
name: 'Status',
label: 'Status',
type: 'select',
value: '',
options: [
{ label: 'Active', value: 'Active' },
{ label: 'Inactive', value: 'Inactive' }
]
}
];
// Views
const defaultViews: View[] = [
{
id: 'all',
name: 'All Locations',
configurations: {
filters: {}
}
},
{
id: 'buildings',
name: 'Buildings',
configurations: {
filters: { LocationType: 'Building' }
}
},
{
id: 'floors',
name: 'Floors',
configurations: {
filters: { LocationType: 'Floor' }
}
},
{
id: 'active',
name: 'Active',
configurations: {
filters: { Status: 'Active' }
}
}
];
const viewsConfig: ViewsConfig = {
listId: 'location-portfolio',
defaultViews: defaultViews,
allowToMangeCustomViews: true,
defaultviewId: 'all'
};
// Pluggable details panel
const { PluggableComponent: DetailsPanel } = usePluggableView({
pluginId: 'location-details-panel',
defaultComponent: LocationDetailsPanel,
props: {
locationKey: selectedKey || '',
onClose: () => {
if (navigate) {
const url = generateURL({ removeParams: ['selectedKey'] });
navigate(url);
}
}
}
});
const detailsPanel: CustomDetailsPanelProps = {
renderDetails: (itemId: string | number, onClose: () => void) => (
<DetailsPanel />
),
collapsed: true
};
return (
<>
<ObjectSearchComponent
data={getAll}
idField="Key"
columns={columns}
pageSize={50}
filters={{
fields: filterFields
}}
views={viewsConfig}
actionButtons={
<button onClick={() => {
setEditingLocation(null);
setShowForm(true);
}}>
Add Location
</button>
}
detailsPanel={detailsPanel}
key={refreshKey}
/>
{showForm && (
<LocationFormComponent
location={editingLocation}
onClose={() => {
setShowForm(false);
setEditingLocation(null);
}}
onSave={() => {
setShowForm(false);
setEditingLocation(null);
setRefreshKey(prev => prev + 1);
}}
/>
)}
</>
);
};
export default PortfolioView;