map addition
This commit is contained in:
parent
c2c9ac39da
commit
b1003de515
@ -31,16 +31,23 @@ export interface ChartConfig {
|
|||||||
|
|
||||||
export interface AnalyticsChartProps {
|
export interface AnalyticsChartProps {
|
||||||
data?: unknown[];
|
data?: unknown[];
|
||||||
|
gridCols?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AnalyticsChart({ data }: AnalyticsChartProps) {
|
export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
||||||
if (!data || !Array.isArray(data) || data.length === 0) return null;
|
if (!data || !Array.isArray(data) || data.length === 0) return null;
|
||||||
|
|
||||||
const charts = data as ChartConfig[];
|
const charts = data as ChartConfig[];
|
||||||
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899', '#14B8A6'];
|
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899', '#14B8A6'];
|
||||||
|
|
||||||
|
const gridClass = gridCols === 1
|
||||||
|
? "grid grid-cols-1 gap-6 w-full"
|
||||||
|
: gridCols === 2
|
||||||
|
? "grid grid-cols-1 lg:grid-cols-2 gap-6 w-full"
|
||||||
|
: "grid grid-cols-1 lg:grid-cols-3 gap-6 w-full";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 w-full">
|
<div className={gridClass}>
|
||||||
{charts.map((chart, idx) => {
|
{charts.map((chart, idx) => {
|
||||||
const title = (chart.key || `Chart ${idx + 1}`)
|
const title = (chart.key || `Chart ${idx + 1}`)
|
||||||
.replace(/_/g, ' ')
|
.replace(/_/g, ' ')
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { RecordView } from './RecordView';
|
|||||||
import type { WiredRecordViewProps } from './OrdersView';
|
import type { WiredRecordViewProps } from './OrdersView';
|
||||||
|
|
||||||
/** Daily Logs record view (Daily Reports workflow). */
|
/** Daily Logs record view (Daily Reports workflow). */
|
||||||
export function DailyLogsView({ onRowClick, pageSize, headerActions, rowActions, refreshKey, presetAlias }: WiredRecordViewProps) {
|
export function DailyLogsView({ onRowClick, pageSize, headerActions, rowActions, refreshKey, presetAlias, mapComponent }: WiredRecordViewProps) {
|
||||||
return (
|
return (
|
||||||
<RecordView
|
<RecordView
|
||||||
client={dailyReportsClient}
|
client={dailyReportsClient}
|
||||||
@ -16,6 +16,7 @@ export function DailyLogsView({ onRowClick, pageSize, headerActions, rowActions,
|
|||||||
rowActions={rowActions}
|
rowActions={rowActions}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
presetAlias={presetAlias}
|
presetAlias={presetAlias}
|
||||||
|
mapComponent={mapComponent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export interface WiredRecordViewProps {
|
|||||||
refreshKey?: number;
|
refreshKey?: number;
|
||||||
initialFilters?: Record<string, string>;
|
initialFilters?: Record<string, string>;
|
||||||
presetAlias?: string;
|
presetAlias?: string;
|
||||||
|
mapComponent?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Orders record view (Order Booking workflow). */
|
/** Orders record view (Order Booking workflow). */
|
||||||
|
|||||||
@ -43,6 +43,8 @@ export interface RecordViewProps {
|
|||||||
initialFilters?: Record<string, string>;
|
initialFilters?: Record<string, string>;
|
||||||
/** Custom preset alias to send with the request (e.g. preset_alias: my_orders) */
|
/** Custom preset alias to send with the request (e.g. preset_alias: my_orders) */
|
||||||
presetAlias?: string;
|
presetAlias?: string;
|
||||||
|
/** Component to render next to charts (e.g., a map) */
|
||||||
|
mapComponent?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,6 +68,7 @@ export function RecordView({
|
|||||||
omitColumns,
|
omitColumns,
|
||||||
initialFilters = {},
|
initialFilters = {},
|
||||||
presetAlias,
|
presetAlias,
|
||||||
|
mapComponent,
|
||||||
}: RecordViewProps) {
|
}: RecordViewProps) {
|
||||||
const [internalPageSize, setInternalPageSize] = useState(pageSize);
|
const [internalPageSize, setInternalPageSize] = useState(pageSize);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
@ -150,12 +153,26 @@ export function RecordView({
|
|||||||
const tileValues = resp?.tile_values;
|
const tileValues = resp?.tile_values;
|
||||||
|
|
||||||
const chartData = resp?.chart_data;
|
const chartData = resp?.chart_data;
|
||||||
|
const hasChart = chartData && Array.isArray(chartData) && chartData.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 w-full">
|
<div className="flex flex-col gap-6 w-full">
|
||||||
<StatsTiles tiles={tileValues} />
|
<StatsTiles tiles={tileValues} />
|
||||||
|
|
||||||
<AnalyticsChart data={chartData} />
|
{(hasChart || mapComponent) ? (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 w-full">
|
||||||
|
{hasChart && (
|
||||||
|
<div className={mapComponent ? "lg:col-span-1" : "lg:col-span-3"}>
|
||||||
|
<AnalyticsChart data={chartData} gridCols={mapComponent ? 1 : undefined} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{mapComponent && (
|
||||||
|
<div className={hasChart ? "lg:col-span-2" : "lg:col-span-3"}>
|
||||||
|
{mapComponent}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
title={title}
|
title={title}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { RecordView } from './RecordView';
|
|||||||
import type { WiredRecordViewProps } from './OrdersView';
|
import type { WiredRecordViewProps } from './OrdersView';
|
||||||
|
|
||||||
/** Store record view (Store workflow). */
|
/** Store record view (Store workflow). */
|
||||||
export function StoresView({ onRowClick, pageSize, headerActions, rowActions, refreshKey }: WiredRecordViewProps & { headerActions?: React.ReactNode }) {
|
export function StoresView({ onRowClick, pageSize, headerActions, rowActions, refreshKey, mapComponent }: WiredRecordViewProps & { headerActions?: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<RecordView
|
<RecordView
|
||||||
client={storeClient}
|
client={storeClient}
|
||||||
@ -15,6 +15,7 @@ export function StoresView({ onRowClick, pageSize, headerActions, rowActions, re
|
|||||||
headerActions={headerActions}
|
headerActions={headerActions}
|
||||||
rowActions={rowActions}
|
rowActions={rowActions}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
|
mapComponent={mapComponent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { DailyLogsView } from '../components/rv';
|
|||||||
import { DailyLogDetail } from '../components/dv';
|
import { DailyLogDetail } from '../components/dv';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button } from '../components/buttons/Button';
|
import { Button } from '../components/buttons/Button';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus, MapPin } from 'lucide-react';
|
||||||
import { DynamicForm } from '../components/forms/DynamicForm';
|
import { DynamicForm } from '../components/forms/DynamicForm';
|
||||||
import { DAILY_REPORTS } from '../api/config';
|
import { DAILY_REPORTS } from '../api/config';
|
||||||
import { dailyReportsClient } from '../api/clients';
|
import { dailyReportsClient } from '../api/clients';
|
||||||
@ -19,10 +19,31 @@ export function DailyLogsPage() {
|
|||||||
const [punchOutTitle, setPunchOutTitle] = useState('Punch Out');
|
const [punchOutTitle, setPunchOutTitle] = useState('Punch Out');
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
|
||||||
|
const mapComponent = (
|
||||||
|
<div className="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden flex flex-col h-full min-h-[320px]">
|
||||||
|
<div className="p-4 border-b border-slate-100 flex items-center gap-2">
|
||||||
|
<MapPin size={18} className="text-slate-400" />
|
||||||
|
<h3 className="text-slate-800 font-bold">Activity Locations</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 relative bg-slate-50">
|
||||||
|
<iframe
|
||||||
|
title="Logs Map"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{ border: 0, position: 'absolute', inset: 0 }}
|
||||||
|
loading="lazy"
|
||||||
|
allowFullScreen
|
||||||
|
src="https://maps.google.com/maps?q=activity&hl=en&z=10&output=embed"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{instanceId == null && (
|
{instanceId == null && (
|
||||||
<DailyLogsView
|
<DailyLogsView
|
||||||
|
mapComponent={mapComponent}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
presetAlias="my_logs"
|
presetAlias="my_logs"
|
||||||
onRowClick={(row) => {
|
onRowClick={(row) => {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { StoresView } from '../components/rv';
|
import { StoresView } from '../components/rv';
|
||||||
import { StoreDetail } from '../components/dv';
|
import { StoreDetail } from '../components/dv';
|
||||||
import { Button } from '../components/buttons/Button';
|
import { Button } from '../components/buttons/Button';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus, MapPin } from 'lucide-react';
|
||||||
import { DynamicForm } from '../components/forms/DynamicForm';
|
import { DynamicForm } from '../components/forms/DynamicForm';
|
||||||
import { STORE } from '../api/config';
|
import { STORE } from '../api/config';
|
||||||
import { storeClient } from '../api/clients';
|
import { storeClient } from '../api/clients';
|
||||||
@ -55,9 +55,30 @@ export function StoresPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapComponent = (
|
||||||
|
<div className="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden flex flex-col h-full min-h-[320px]">
|
||||||
|
<div className="p-4 border-b border-slate-100 flex items-center gap-2">
|
||||||
|
<MapPin size={18} className="text-slate-400" />
|
||||||
|
<h3 className="text-slate-800 font-bold">Location Spread</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 relative bg-slate-50">
|
||||||
|
<iframe
|
||||||
|
title="Stores Map"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{ border: 0, position: 'absolute', inset: 0 }}
|
||||||
|
loading="lazy"
|
||||||
|
allowFullScreen
|
||||||
|
src="https://maps.google.com/maps?q=stores&hl=en&z=10&output=embed"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StoresView
|
<StoresView
|
||||||
|
mapComponent={mapComponent}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
onRowClick={(row) => {
|
onRowClick={(row) => {
|
||||||
const id = row.instance_id as number | string | undefined;
|
const id = row.instance_id as number | string | undefined;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user