fix: build errors and error handling refactored

This commit is contained in:
suryac 2026-10-05 12:21:09 +05:30
parent 13160aecb4
commit a04d469b61
5 changed files with 127 additions and 8 deletions

1
.gitignore vendored
View File

@ -25,3 +25,4 @@ dist-ssr
# vite-plugin-pwa dev output # vite-plugin-pwa dev output
dev-dist dev-dist
.agents/

124
README.md
View File

@ -40,3 +40,127 @@ These routes require the user to be logged in. Roles determine access.
- `/admin/datasets` - Manage Datasets (Admin Only) - `/admin/datasets` - Manage Datasets (Admin Only)
- `/admin/datasets/:id` - Manage Dataset Items (Admin Only) - `/admin/datasets/:id` - Manage Dataset Items (Admin Only)
## Architecture & Workflows (How Things Work)
This app interfaces with a generic backend platform via `ZinoClient` (see `src/api/client.ts`). The business logic is driven by configuring Workflows, Activities, and Views.
### 1. Workflows
The app is divided into three primary Workflows, mapped out in `src/api/config.ts`:
- **ORDER_BOOKING**: Manages everything from logging a visit, taking an order, to closing an order.
- **STORE**: Manages the creation and editing of stores/retailers.
- **DAILY_REPORTS**: Manages the Sales Officer's daily punch-in (Day Plan) and punch-out (End of Day report).
Each workflow has a unique `workflowUuid` and `versionUuid`.
### 2. How to Add or Start a New Instance
When a user submits a form to create something new (e.g., adding a new Store or starting a new Daily Log), the app "starts an instance" of that specific workflow.
1. **Instantiate the Client**: A `ZinoClient` is created for the specific workflow.
```typescript
const client = new ZinoClient(BASE_URL, STORE.workflowUuid);
```
2. **Start the Instance**: The app calls the API to begin the first activity (e.g., `INIT` activity) with the form payload.
```typescript
const res = await client.startInstance(
STORE.activities.INIT.uid,
payload,
{ version_uuid: STORE.versionUuid }
);
```
3. **Progressing the Instance**: If the workflow has multiple steps (like Order Booking, which progresses from `LOG_VISIT` to `PLACE_ORDER` to `CLOSE_ORDER`), subsequent updates use `progressInstance` with the existing `instance_id`.
```typescript
const res = await client.progressInstance(
instanceId,
ORDER_BOOKING.activities.PLACE_ORDER.uid,
payload
);
```
### 3. Views (Listing Data)
To display lists of data (like the tables on the `/orders` or `/stores` pages), the app queries "Record Views" configured on the backend.
```typescript
const data = await client.listInstances(STORE.recordViews.STORE, filters);
```
These views return tabular data containing the latest state of all instances in that workflow.
### 4. Workflows Deep Dive & Activity Chains
#### 4.1 Order Booking Workflow
Manages the end-to-end process of visiting a store and taking an order.
**Activity Chain:**
`LOG_VISIT` ➔ `PLACE_ORDER` ➔ `EDIT_ORDER` ➔ `PRODUCTIVITY_OF_VISIT` ➔ `POTENTIAL_MINING` ➔ `CLOSE_ORDER`
- **LOG_VISIT (Init)**
- Fields: `select_store` (wf_lookup), `date_of_visit` (date), `time_of_visit` (time), `upload_image` (image)
- **PLACE_ORDER**
- Fields: `date_of_order` (date), `order_details` (grid), `total_bags` (number), `total_kgs` (number)
- **EDIT_ORDER**
- Fields: Same as Place Order, allowing modifications before closure.
- **PRODUCTIVITY_OF_VISIT**
- Fields: `is_productive_call` (radio), `store_status` (select), `order_received_channel` (select), `remarks` (longtext), `action` (radio)
- **POTENTIAL_MINING**
- Fields: `potential` (grid - tracks actual potential vs ordered)
- **CLOSE_ORDER**
- Fields: `remarks_2` (longtext)
#### 4.2 Store Workflow
Manages onboarding and updating retailers in the system.
**Activity Chain:**
`INIT` ➔ `EDIT_STORE`
- **INIT (Store Creation)**
- Fields: `business_name_2` (text), `area_2` (text), `complete_address_2` (longtext), `pin_code_2` (number), `owner_name_2` (text), `phone_number_2` (phone), `email_2` (email), `store_location_2` (geolocation), `store_image_2` (image), `route_name_2` (select), `route_code_2` (select), `sub_route_2` (select), `distributor_name_2` (select), `distributor_owner_name_2` (text), `distributor_email_2` (email), `distributor_phone_number_2` (phone), `notes_2` (longtext), `potential_2` (grid)
- **EDIT_STORE**
- Fields: Matches the INIT fields (uses `_3` suffix) to allow updates to store metadata.
#### 4.3 Daily Reports Workflow
Tracks Sales Officer attendance, daily planning, and end-of-day reports.
**Activity Chain:**
`INIT` (Day Plan) ➔ `PUNCH_OUT` (EOD)
- **INIT (Punch In)**
- Fields: `sales_officer_name` (app_user), `date` (date), `time` (time), `route_code` (select), `sub_route` (select), `store_image` (image), `day_plan_notes` (longtext)
- **PUNCH_OUT (Punch Out)**
- Fields: `total_productive_calls` (number), `total_non_productive_calls` (number), `eod_notes_remarks` (longtext)
#### 4.4 User Management Workflow
Manages Sales Officers and their credentials.
**Activity Chain:**
`ADD_USER` ➔ `EDIT_USER`
- **ADD_USER**
- Fields: `name` (text), `email` (email), `password` (password)
- **EDIT_USER**
- Fields: `name_3` (text), `email_3` (email)
### 5. Custom Pipelines
The app uses backend pipelines (custom API endpoints) to fetch aggregated data that standard Record Views cannot provide. These are defined in `src/api/config.ts`:
- `/api/p/krishna-group/v1/nearest-stores`: Fetches stores near the user's geolocation for logging visits. **(Used in: `NearestStoresMap.tsx`)**
- `/api/p/krishna-group/v1/daily-sales-report`: Aggregates the daily sales report for managers. **(Used in: `DailySalesReportPage.tsx` via `client.ts`)**
- `/api/p/krishna-group/v1/productive-call-summary`: Summarizes productive vs non-productive calls. **(Used in: `DynamicForm.tsx` during order workflows)**
- `/api/p/krishna-group/v1/sales-officers`: Retrieves a list of Sales Officers for filtering. **(Used in: `DailySalesReportPage.tsx`)**
- `/api/p/krishna-group/v1/auto-close-orders`: Batch process to close pending orders. **(Defined in config, executed via backend CRON or specific admin actions)**
- `/api/p/krishna-group/v1/edit-orders`: Pipeline to handle order modifications. **(Configured for future implementation)**
- `/api/p/krishna-group/v1/orders`: Fetches order summaries. **(Configured for future implementation)**
- `/api/p/krishna-group/v1/potential-mining`: Retrieves potential data for stores to pre-fill the smart grid. **(Used in: `DynamicForm.tsx`, `CallDetail.tsx`, `OrdersPage.tsx`, `CallsPage.tsx`)**
### 6. Admin Pages
The application includes two dedicated administrative panels restricted to users with the Admin role (and sometimes Manager).
- **Manage Datasets (`/admin/datasets`)**:
Connects to Zino's dataset API via `client.getDatasets()`. It dynamically renders a dual-pane UI where admins can select a dataset (either a `List` or a `Table` of objects) from the left sidebar and manage its items on the right via `DatasetItemsPage.tsx`. This allows admins to update core master data (like product lists, route configurations) without deploying new code.
- **Manage Users (`/admin/users`)**:
Operates on the `USER` workflow (`ADD_USER`, `EDIT_USER` activities) to onboard new Sales Officers and edit their profiles or access roles, using standard workflow instances.
### 7. Daily Sales Report (DSR)
The **Daily Sales Report (`/sales-report`)** page provides managers and administrators with a consolidated view of all orders booked on a specific day.
#### How it works:
- **Filtering**: The user selects a specific Date, Route, Distributor, and Sales Officer.
- **Pipeline Aggregation**: Instead of relying on raw workflow lists, it queries a custom pipeline (`/api/p/krishna-group/v1/daily-sales-report`) that heavily aggregates order data from the backend, matching the selected filters.
- **PDF Generation**: It uses `jspdf` and `jspdf-autotable` to generate a dynamic, printable PDF in landscape mode.
- **SKU Chunking**: Because a store might order many different SKUs across multiple brands, the PDF logic dynamically splits columns (SKUs) into printable chunks (`PDF_MAX_SKUS_PER_CHUNK` defined in config) so that tables never overflow the page horizontally. It generates subsequent pages for overflowed SKUs automatically.

View File

@ -781,7 +781,6 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
onChange={(newVal) => handleFieldChange(f.id, newVal)} onChange={(newVal) => handleFieldChange(f.id, newVal)}
isActivityPotentialMining={currentActivityId === ORDER_BOOKING.activities.POTENTIAL_MINING.uid} isActivityPotentialMining={currentActivityId === ORDER_BOOKING.activities.POTENTIAL_MINING.uid}
required={f.mandatory} required={f.mandatory}
error={!!backendErrors[f.id]}
/> />
); );
} }

View File

@ -12,7 +12,6 @@ export function SmartGridField({
onChange, onChange,
isActivityPotentialMining = false, isActivityPotentialMining = false,
required = false, required = false,
error = false,
}: { }: {
label: string; label: string;
columns: FormScreenField[]; columns: FormScreenField[];
@ -20,7 +19,6 @@ export function SmartGridField({
onChange: (val: Record<string, unknown>[]) => void; onChange: (val: Record<string, unknown>[]) => void;
isActivityPotentialMining?: boolean; isActivityPotentialMining?: boolean;
required?: boolean; required?: boolean;
error?: boolean;
}) { }) {
const isPotentialMining = isActivityPotentialMining; const isPotentialMining = isActivityPotentialMining;
const formRef = useRef<HTMLDivElement>(null); const formRef = useRef<HTMLDivElement>(null);

View File

@ -1,4 +1,3 @@
import { useState } from 'react';
import { Input } from '../../reusable/Input'; import { Input } from '../../reusable/Input';
export function TextField({ export function TextField({
@ -18,8 +17,6 @@ export function TextField({
disabled?: boolean; disabled?: boolean;
readOnly?: boolean; readOnly?: boolean;
}) { }) {
const [touched, setTouched] = useState(false);
const isPincode = label.toLowerCase().replace(/\s/g, '') === 'pincode'; const isPincode = label.toLowerCase().replace(/\s/g, '') === 'pincode';
return ( return (
@ -36,7 +33,6 @@ export function TextField({
} }
onChange(val); onChange(val);
}} }}
onBlur={() => setTouched(true)}
disabled={disabled} disabled={disabled}
readOnly={readOnly} readOnly={readOnly}
pattern={isPincode ? '^\\d{6}$' : undefined} pattern={isPincode ? '^\\d{6}$' : undefined}
@ -45,3 +41,4 @@ export function TextField({
); );
} }