diff --git a/src/App.tsx b/src/App.tsx index 66b914c..1a5f7a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,8 +5,10 @@ import { ConsoleLayout } from './screens/ConsoleLayout' import { OrdersPage } from './screens/OrdersPage' import { MyOrdersPage } from './screens/MyOrdersPage' import { CallsPage } from './screens/CallsPage' +import { MyCallsPage } from './screens/MyCallsPage' import { StoresPage } from './screens/StoresPage' import { DailyLogsPage } from './screens/DailyLogsPage' +import { MyDailyLogsPage } from './screens/MyDailyLogsPage' import { DailySalesReportPage } from './screens/DailySalesReportPage' import { ReportPage } from './screens/ReportPage' @@ -26,12 +28,18 @@ function App() { } /> } /> + } /> + } /> + } /> } /> } /> } /> + } /> + } /> + } /> } /> } /> diff --git a/src/components/rv/CallsView.tsx b/src/components/rv/CallsView.tsx index 91c9535..93c7267 100644 --- a/src/components/rv/CallsView.tsx +++ b/src/components/rv/CallsView.tsx @@ -4,7 +4,7 @@ import { RecordView } from './RecordView'; import type { WiredRecordViewProps } from './OrdersView'; /** Calls record view (Calls/Visits workflow). */ -export function CallsView({ onRowClick, pageSize, headerActions, rowActions, refreshKey }: WiredRecordViewProps) { +export function CallsView({ onRowClick, pageSize, headerActions, rowActions, refreshKey, initialFilters, presetAlias }: WiredRecordViewProps) { return ( ); } diff --git a/src/components/rv/DailyLogsView.tsx b/src/components/rv/DailyLogsView.tsx index d63d4b0..6e419ea 100644 --- a/src/components/rv/DailyLogsView.tsx +++ b/src/components/rv/DailyLogsView.tsx @@ -4,7 +4,7 @@ import { RecordView } from './RecordView'; import type { WiredRecordViewProps } from './OrdersView'; /** Daily Logs record view (Daily Reports workflow). */ -export function DailyLogsView({ onRowClick, pageSize, headerActions, rowActions, refreshKey, presetAlias, mapComponent }: WiredRecordViewProps) { +export function DailyLogsView({ onRowClick, pageSize, headerActions, rowActions, refreshKey, presetAlias, initialFilters, mapComponent }: WiredRecordViewProps) { return ( ); diff --git a/src/screens/MyCallsPage.tsx b/src/screens/MyCallsPage.tsx new file mode 100644 index 0000000..6660e81 --- /dev/null +++ b/src/screens/MyCallsPage.tsx @@ -0,0 +1,207 @@ +import { useNavigate, useParams } from 'react-router-dom'; +import { Modal } from '../components/reusable'; +import { CallsView } from '../components/rv'; +import { CallDetail } from '../components/dv'; +import { useEffect, useState } from 'react'; +import { Button } from '../components/buttons/Button'; +import { Plus } from 'lucide-react'; +import { DynamicForm } from '../components/forms/DynamicForm'; +import { ORDER_BOOKING } from '../api/config'; +import { orderBookingClient } from '../api/clients'; +import { useAuth } from '../auth/context'; + +export function MyCallsPage() { + const params = useParams(); + const instanceId = params.instanceId ? Number(params.instanceId) : undefined; + const navigate = useNavigate(); + const [isCreating, setIsCreating] = useState(false); + const [createTitle, setCreateTitle] = useState('Log Visit'); + const [refreshKey, setRefreshKey] = useState(0); + const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null); + + const [selectedRow, setSelectedRow] = useState | null>(null); + const [miningLoading, setMiningLoading] = useState(false); + const [miningPrefill, setMiningPrefill] = useState | undefined>(); + + const { userEmail } = useAuth(); + + useEffect(() => { + if (instanceId != null) { + orderBookingClient.recordView(ORDER_BOOKING.recordViews.CALLS, { + page: 1, + limit: 1, + filters: [{ field_key: 'instance_id', value: String(instanceId), data_type: 'number' }] + }).then(res => { + if (res.data && res.data.length > 0) { + setSelectedRow(res.data[0]); + } + }).catch(err => console.error("Failed to fetch selected row:", err)); + } + }, [instanceId, refreshKey]); + + const handlePotentialMiningClick = async () => { + setMiningLoading(true); + setMiningPrefill(undefined); + try { + const storeCode = selectedRow?.store_code || selectedRow?.code || selectedRow?.store?.store_code; + + if (!storeCode) { + console.warn("Store code not found on selected row, proceeding anyway."); + } + + const payload = { + store_code: storeCode, + instance_id: String(instanceId) + }; + + const response = await orderBookingClient.request<{ potential: { potential: any[] } }>( + 'POST', + '/api/papi2/potential-mining', + payload, + { 'TemplateID': '146' } + ); + + const rawPotential = response.potential?.potential || []; + const mappedPotential = rawPotential.map((row: any) => { + const cat = row.product_category || row.product_category_ || row.category; + return { + ...row, + product_category_: cat, + product_category: cat, + productcategory: cat, + category: cat, + product_category_1: cat + }; + }); + + setMiningPrefill({ potential: mappedPotential }); + setActiveActivity({ id: ORDER_BOOKING.activities.POTENTIAL_MINING.uid, name: 'Potential Mining' }); + } catch (e: any) { + alert("Failed to fetch potential mining data: " + (e.message || "Unknown error")); + } finally { + setMiningLoading(false); + } + }; + + if (instanceId != null) { + return ( +
+
+ {selectedRow ? ( + navigate('/my-calls')} + selectedRow={selectedRow} + refreshKey={refreshKey} + potentialMiningAction={ + + } + placeOrderAction={ + (() => { + const stateName = String(selectedRow.current_state_name || selectedRow.current_state_name_ || selectedRow.current_state || selectedRow.status || '').toLowerCase(); + if (stateName === 'no order' || stateName === 'no_order') { + return null; + } else if (stateName.includes('ordered') || stateName === 'ordered' || stateName.includes('order')) { + return ( + + ); + } else if (stateName.includes('visited')) { + return ( + + ); + } else if (stateName.includes('productive')) { + return ( + + ); + } + return ( + + ); + })() + } + /> + ) : ( +
Loading call details...
+ )} +
+ + {/* Active Activity Modal (for actions) */} + setActiveActivity(null)} + title={activeActivity?.name} + width={activeActivity?.id === ORDER_BOOKING.activities.POTENTIAL_MINING.uid ? 'lg' : 'md'} + > + {activeActivity && instanceId != null && ( + { + setActiveActivity(null); + setRefreshKey(k => k + 1); + }} + onCancel={() => setActiveActivity(null)} + onActivityChange={(name) => setActiveActivity(prev => prev ? { ...prev, name } : null)} + /> + )} + +
+ ); + } + + return ( + <> + { + setSelectedRow(row); + const id = row.instance_id as number | string | undefined; + if (id != null) navigate(`/my-calls/${id}`); + }} + headerActions={ + + } + /> + + setIsCreating(false)} + title={createTitle} + width="md" + > + { + setIsCreating(false); + setRefreshKey(k => k + 1); + }} + onCancel={() => setIsCreating(false)} + onActivityChange={setCreateTitle} + /> + + + ); +} diff --git a/src/screens/MyDailyLogsPage.tsx b/src/screens/MyDailyLogsPage.tsx new file mode 100644 index 0000000..c4cb24a --- /dev/null +++ b/src/screens/MyDailyLogsPage.tsx @@ -0,0 +1,135 @@ +import { useNavigate, useParams } from 'react-router-dom'; +import { Modal } from '../components/reusable'; +import { DailyLogsView } from '../components/rv'; +import { DailyLogDetail } from '../components/dv'; +import { useState } from 'react'; +import { Button } from '../components/buttons/Button'; +import { Plus, MapPin } from 'lucide-react'; +import { DynamicForm } from '../components/forms/DynamicForm'; +import { DAILY_REPORTS } from '../api/config'; +import { dailyReportsClient } from '../api/clients'; +import { useAuth } from '../auth/context'; + +export function MyDailyLogsPage() { + const params = useParams(); + const instanceId = params.instanceId ? Number(params.instanceId) : undefined; + const navigate = useNavigate(); + const [isCreating, setIsCreating] = useState(false); + const [createTitle, setCreateTitle] = useState('Punch In'); + const [punchOutInstanceId, setPunchOutInstanceId] = useState(null); + const [punchOutTitle, setPunchOutTitle] = useState('Punch Out'); + const [refreshKey, setRefreshKey] = useState(0); + + const { userEmail } = useAuth(); + + const mapComponent = ( +
+
+ +

Activity Locations

+
+
+ +
+
+ ); + + return ( + <> + {instanceId == null && ( + { + const id = row.instance_id as number | string | undefined; + if (id != null) navigate(`/my-daily/${id}`); + }} + headerActions={ + + } + rowActions={(row) => { + const state = String(row.current_state_name || row.current_state_name_ || row.current_state || row.status || '').toLowerCase(); + // Show only for Punched In, hide for Punched Out or others + if (state.includes('out') || !state.includes('in')) return null; + + return ( + + ); + }} + /> + )} + + setIsCreating(false)} + title={createTitle} + width="md" + > + { + setIsCreating(false); + setRefreshKey(k => k + 1); + }} + onCancel={() => setIsCreating(false)} + onActivityChange={setCreateTitle} + /> + + + setPunchOutInstanceId(null)} + title={punchOutTitle} + width="md" + > + {punchOutInstanceId != null && ( + { + setPunchOutInstanceId(null); + setRefreshKey(k => k + 1); + }} + onCancel={() => setPunchOutInstanceId(null)} + onActivityChange={setPunchOutTitle} + /> + )} + + + {instanceId != null && ( +
+ setPunchOutInstanceId(instanceId)} + onBack={() => navigate('/my-daily')} + /> +
+ )} + + ); +} diff --git a/src/screens/tabs.ts b/src/screens/tabs.ts index f9a47c1..97d8d0c 100644 --- a/src/screens/tabs.ts +++ b/src/screens/tabs.ts @@ -14,7 +14,7 @@ import { type WiredDetailViewProps, } from '../components/dv'; -export type ScreenKey = 'orders' | 'my-orders' | 'calls' | 'stores' | 'daily' | 'sales-report'; +export type ScreenKey = 'orders' | 'my-orders' | 'calls' | 'my-calls' | 'stores' | 'daily' | 'my-daily' | 'sales-report'; export interface ScreenDef { key: ScreenKey; @@ -30,9 +30,11 @@ export const SCREENS: ScreenDef[] = [ { key: 'orders', label: 'Orders', icon: ShoppingCart, View: OrdersView, Detail: OrderDetail, noun: 'Order' }, { key: 'my-orders', label: 'My Orders', icon: ShoppingCart, View: OrdersView, Detail: OrderDetail, noun: 'My Order' }, { key: 'calls', label: 'Calls', icon: Phone, View: CallsView, Detail: CallDetail, noun: 'Call' }, + { key: 'my-calls', label: 'My Calls', icon: Phone, View: CallsView, Detail: CallDetail, noun: 'My Call' }, { key: 'stores', label: 'Stores', icon: Store, View: StoresView, Detail: StoreDetail, noun: 'Store' }, { key: 'daily', label: 'Daily Logs', icon: ClipboardList, View: DailyLogsView, Detail: DailyLogDetail, noun: 'Daily Log' }, - { key: 'sales-report', label: 'Sales Report', icon: FileText, View: null as any, Detail: null as any, noun: 'Sales Report' }, + { key: 'my-daily', label: 'My Daily Logs', icon: ClipboardList, View: DailyLogsView, Detail: DailyLogDetail, noun: 'My Daily Log' }, + { key: 'sales-report', label: 'DSR', icon: FileText, View: null as any, Detail: null as any, noun: 'Sales Report' }, ]; export function screenByKey(key: string | undefined): ScreenDef | undefined {