added map
This commit is contained in:
parent
ea0aa8ae58
commit
7d6f1a9021
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@ dist-ssr
|
|||||||
|
|
||||||
# vite-plugin-pwa dev output
|
# vite-plugin-pwa dev output
|
||||||
dev-dist
|
dev-dist
|
||||||
|
.env
|
||||||
|
|||||||
91
package-lock.json
generated
91
package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "krishna-sales-pwa",
|
"name": "krishna-sales-pwa",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-google-maps/api": "^2.20.8",
|
||||||
"jspdf": "^4.2.1",
|
"jspdf": "^4.2.1",
|
||||||
"jspdf-autotable": "^5.0.8",
|
"jspdf-autotable": "^5.0.8",
|
||||||
"lucide-react": "^0.400.0",
|
"lucide-react": "^0.400.0",
|
||||||
@ -1745,6 +1746,22 @@
|
|||||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@googlemaps/js-api-loader": {
|
||||||
|
"version": "1.16.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz",
|
||||||
|
"integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@googlemaps/markerclusterer": {
|
||||||
|
"version": "2.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
|
||||||
|
"integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"supercluster": "^8.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@ -2304,6 +2321,36 @@
|
|||||||
"url": "https://github.com/sponsors/sxzz"
|
"url": "https://github.com/sponsors/sxzz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-google-maps/api": {
|
||||||
|
"version": "2.20.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.20.8.tgz",
|
||||||
|
"integrity": "sha512-wtLYFtCGXK3qbIz1H5to3JxbosPnKsvjDKhqGylXUb859EskhzR7OpuNt0LqdLarXUtZCJTKzPn3BNaekNIahg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@googlemaps/js-api-loader": "1.16.8",
|
||||||
|
"@googlemaps/markerclusterer": "2.5.3",
|
||||||
|
"@react-google-maps/infobox": "2.20.0",
|
||||||
|
"@react-google-maps/marker-clusterer": "2.20.0",
|
||||||
|
"@types/google.maps": "3.58.1",
|
||||||
|
"invariant": "2.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17 || ^18 || ^19",
|
||||||
|
"react-dom": "^16.8 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-google-maps/infobox": {
|
||||||
|
"version": "2.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.20.0.tgz",
|
||||||
|
"integrity": "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@react-google-maps/marker-clusterer": {
|
||||||
|
"version": "2.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.20.0.tgz",
|
||||||
|
"integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@reduxjs/toolkit": {
|
"node_modules/@reduxjs/toolkit": {
|
||||||
"version": "2.12.0",
|
"version": "2.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz",
|
||||||
@ -3463,6 +3510,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/google.maps": {
|
||||||
|
"version": "3.58.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
|
||||||
|
"integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@ -5120,7 +5173,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
@ -5743,6 +5795,15 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/invariant": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iobuffer": {
|
"node_modules/iobuffer": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
|
||||||
@ -6256,7 +6317,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jsesc": {
|
"node_modules/jsesc": {
|
||||||
@ -6355,6 +6415,12 @@
|
|||||||
"jspdf": "^2 || ^3 || ^4"
|
"jspdf": "^2 || ^3 || ^4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kdbush": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@ -6680,6 +6746,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"loose-envify": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -7988,6 +8066,15 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supercluster": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"kdbush": "^4.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/supports-preserve-symlinks-flag": {
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"generate-pwa-assets": "pwa-assets-generator"
|
"generate-pwa-assets": "pwa-assets-generator"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-google-maps/api": "^2.20.8",
|
||||||
"jspdf": "^4.2.1",
|
"jspdf": "^4.2.1",
|
||||||
"jspdf-autotable": "^5.0.8",
|
"jspdf-autotable": "^5.0.8",
|
||||||
"lucide-react": "^0.400.0",
|
"lucide-react": "^0.400.0",
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type {
|
|||||||
RecordViewResponse,
|
RecordViewResponse,
|
||||||
User,
|
User,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { APP_ID } from './config';
|
import { APP_ID, PIPELINE } from './config';
|
||||||
|
|
||||||
const TOKEN_KEY = 'krishna_sales_token';
|
const TOKEN_KEY = 'krishna_sales_token';
|
||||||
|
|
||||||
@ -222,6 +222,15 @@ export class ZinoClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Pipeline APIs ---
|
||||||
|
|
||||||
|
dailySalesReport(data: Record<string, unknown>): Promise<any> {
|
||||||
|
return this.request('POST', PIPELINE.endpoints.dailySalesReport, data, {
|
||||||
|
'TemplateID': '157',
|
||||||
|
'sec-ch-ua-platform': '"Android"',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// --- RDBMS lookup records (owner-agent picker etc.) ---
|
// --- RDBMS lookup records (owner-agent picker etc.) ---
|
||||||
|
|
||||||
/** Search an RDBMS lookup template. Returns the matching rows. `formData`
|
/** Search an RDBMS lookup template. Returns the matching rows. `formData`
|
||||||
|
|||||||
@ -204,3 +204,11 @@ export const SALES_REPORT_DISTRIBUTORS = [
|
|||||||
export const SALES_REPORT_SO_NAMES = [
|
export const SALES_REPORT_SO_NAMES = [
|
||||||
'Surya C'
|
'Surya C'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// --- Pipeline Endpoints ---
|
||||||
|
export const PIPELINE = {
|
||||||
|
endpoints: {
|
||||||
|
nearestStores: '/api/papi2/nearest-stores',
|
||||||
|
dailySalesReport: '/api/papi2/daily-sales-report'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const SIZES: Record<ButtonSize, string> = {
|
|||||||
sm: 'h-[34px] px-3.5 text-[13px] rounded-sm',
|
sm: 'h-[34px] px-3.5 text-[13px] rounded-sm',
|
||||||
md: 'h-[42px] px-[18px] text-base rounded-md',
|
md: 'h-[42px] px-[18px] text-base rounded-md',
|
||||||
lg: 'h-[50px] px-6 text-md rounded-md',
|
lg: 'h-[50px] px-6 text-md rounded-md',
|
||||||
fab: 'h-[56px] w-[56px] text-sm rounded-full !bg-[#1F4D36] text-white shadow-lg',
|
fab: 'h-[56px] w-[56px] text-sm rounded-full !bg-primary text-white shadow-lg',
|
||||||
};
|
};
|
||||||
|
|
||||||
const VARIANTS: Record<ButtonVariant, string> = {
|
const VARIANTS: Record<ButtonVariant, string> = {
|
||||||
|
|||||||
@ -232,7 +232,7 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
{showOrderDetails && row.order_details_3 && Array.isArray(row.order_details_3) && row.order_details_3.length > 0 && (
|
{showOrderDetails && row.order_details_3 && Array.isArray(row.order_details_3) && row.order_details_3.length > 0 && (
|
||||||
<div className="mt-6 border-t border-gray-100 pt-6">
|
<div className="mt-6 border-t border-gray-100 pt-6">
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
|
||||||
<p className="z-card-meta" style={{ margin: 0, color: '#334155', fontWeight: 700 }}>Order Products</p>
|
<p className="z-card-meta m-0 text-slate-700 font-bold">Order Products</p>
|
||||||
<span className="text-xs font-bold text-gray-800 bg-gray-100 px-3 py-1 rounded-full border border-gray-200">
|
<span className="text-xs font-bold text-gray-800 bg-gray-100 px-3 py-1 rounded-full border border-gray-200">
|
||||||
{totalOrders} {Number(totalOrders) === 1 ? 'Item' : 'Items'}
|
{totalOrders} {Number(totalOrders) === 1 ? 'Item' : 'Items'}
|
||||||
</span>
|
</span>
|
||||||
@ -246,9 +246,9 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
const kgsNum = skuNum > 0 ? skuNum * bagsNum : Number(prod.row_kgs_3 || prod.row_kgs || prod.kgs_3 || prod.weight_3 || 0);
|
const kgsNum = skuNum > 0 ? skuNum * bagsNum : Number(prod.row_kgs_3 || prod.row_kgs || prod.kgs_3 || prod.weight_3 || 0);
|
||||||
const kgsStr = kgsNum > 0 ? ` (${kgsNum} kg)` : '';
|
const kgsStr = kgsNum > 0 ? ` (${kgsNum} kg)` : '';
|
||||||
return (
|
return (
|
||||||
<div key={idx} style={{ display: 'flex', justifyContent: 'space-between', fontSize: '14px', background: '#f8fafc', padding: '12px 16px', borderRadius: '8px', border: '1px solid #e2e8f0' }}>
|
<div key={idx} className="flex justify-between text-sm bg-slate-50 px-4 py-3 rounded-lg border border-slate-200">
|
||||||
<span style={{ fontWeight: 700, color: '#1e293b' }}>{productName}</span>
|
<span className="font-bold text-slate-800">{productName}</span>
|
||||||
<span style={{ fontWeight: 800, color: '#3730a3' }}>{bagsStr} Bags{kgsStr}</span>
|
<span className="font-extrabold text-indigo-800">{bagsStr} Bags{kgsStr}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -273,7 +273,7 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
{/* Distributor Details */}
|
{/* Distributor Details */}
|
||||||
{hasDistributor && (
|
{hasDistributor && (
|
||||||
<div className="mt-6 border-t border-gray-100 pt-6">
|
<div className="mt-6 border-t border-gray-100 pt-6">
|
||||||
<p className="z-card-meta" style={{ marginBottom: '12px', color: '#334155', fontWeight: 700 }}>Distributor Details</p>
|
<p className="z-card-meta mb-3 text-slate-700 font-bold">Distributor Details</p>
|
||||||
|
|
||||||
{distName !== '—' && (
|
{distName !== '—' && (
|
||||||
<div className="z-card-store-row" style={{ marginBottom: '12px' }}>
|
<div className="z-card-store-row" style={{ marginBottom: '12px' }}>
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export function OrderCard({ row, fields, isDetailView = false }: { row: Record<s
|
|||||||
const gridField = fields.find(f => f.data_type === 'grid' || String(f.field_key).includes('order_details'));
|
const gridField = fields.find(f => f.data_type === 'grid' || String(f.field_key).includes('order_details'));
|
||||||
const gridValRaw = gridField ? row[gridField.field_key] : (row.order_details || row.order_details_2 || row.order_details_3);
|
const gridValRaw = gridField ? row[gridField.field_key] : (row.order_details || row.order_details_2 || row.order_details_3);
|
||||||
const gridVal = Array.isArray(gridValRaw) ? gridValRaw : [];
|
const gridVal = Array.isArray(gridValRaw) ? gridValRaw : [];
|
||||||
const totalItems = gridVal.length;
|
// const totalItems = gridVal.length;
|
||||||
|
|
||||||
// Totals
|
// Totals
|
||||||
let totalKgsVal = Number(row.total_kgs || row.total_kgs_2 || row.total_kgs_3 || row.total_weight || row.weight || 0);
|
let totalKgsVal = Number(row.total_kgs || row.total_kgs_2 || row.total_kgs_3 || row.total_weight || row.weight || 0);
|
||||||
@ -61,7 +61,7 @@ export function OrderCard({ row, fields, isDetailView = false }: { row: Record<s
|
|||||||
const productsSection = gridVal.length > 0 ? (
|
const productsSection = gridVal.length > 0 ? (
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: '16px',
|
marginTop: '16px',
|
||||||
backgroundColor: '#FFFBF4',
|
backgroundColor: 'var(--tiles-card-bg)',
|
||||||
border: '1px solid #e2e8f0',
|
border: '1px solid #e2e8f0',
|
||||||
borderRadius: '16px',
|
borderRadius: '16px',
|
||||||
padding: '16px'
|
padding: '16px'
|
||||||
@ -84,12 +84,6 @@ export function OrderCard({ row, fields, isDetailView = false }: { row: Record<s
|
|||||||
};
|
};
|
||||||
|
|
||||||
const productName = formatValue(getVal('product_name') || getVal('product_category') || 'Unknown Product');
|
const productName = formatValue(getVal('product_name') || getVal('product_category') || 'Unknown Product');
|
||||||
const skuCodeRaw = getVal('sku_code') || getVal('item_code') || '';
|
|
||||||
const skuCode = isNaN(Number(skuCodeRaw)) || String(skuCodeRaw).length > 3 ? formatValue(skuCodeRaw) : '';
|
|
||||||
const category = formatValue(getVal('product_category') || '');
|
|
||||||
const brCode = formatValue(getVal('br_code') || '');
|
|
||||||
const subtitleArr = [skuCode, category, brCode].filter(v => v && v !== '—');
|
|
||||||
const subtitle = subtitleArr.join(' . ').toUpperCase();
|
|
||||||
|
|
||||||
const bagsStr = formatValue(getVal('bags') || getVal('total_bags') || getVal('quantity') || 0);
|
const bagsStr = formatValue(getVal('bags') || getVal('total_bags') || getVal('quantity') || 0);
|
||||||
const bagsNum = Number(getVal('bags') || getVal('total_bags') || getVal('quantity') || 0);
|
const bagsNum = Number(getVal('bags') || getVal('total_bags') || getVal('quantity') || 0);
|
||||||
@ -107,12 +101,12 @@ export function OrderCard({ row, fields, isDetailView = false }: { row: Record<s
|
|||||||
borderRadius: '24px',
|
borderRadius: '24px',
|
||||||
padding: '12px 20px',
|
padding: '12px 20px',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
<div className="flex flex-col gap-1">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '14px' }}>{productName}</span>
|
<span className="text-primary font-semibold text-sm">{productName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
|
<div className="text-right whitespace-nowrap">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '14px' }}>{bagsStr} Bags</span>
|
<span className="text-primary font-bold text-sm">{bagsStr} Bags</span>
|
||||||
<span style={{ color: 'var(--primary-color)', fontSize: '13px', marginLeft: '6px' }}>({kgsStr})</span>
|
<span className="text-primary text-[13px] ml-1.5">({kgsStr})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -130,7 +124,7 @@ export function OrderCard({ row, fields, isDetailView = false }: { row: Record<s
|
|||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '13px' }}>TOTAL</span>
|
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '13px' }}>TOTAL</span>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '15px' }}>{totalBags} Bags</span>
|
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '15px' }}>{totalBags} Bags</span>
|
||||||
<span style={{ color: 'var(--primary-color)', fontSize: '14px', marginLeft: '6px' }}>({totalKgs} kg)</span>
|
<span className="text-primary text-sm ml-1.5">({totalKgs} kg)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -73,27 +73,27 @@ export function StoreCard({ row, isDetailView = false, onEdit }: { row: Record<s
|
|||||||
<div className="z-card-divider"></div>
|
<div className="z-card-divider"></div>
|
||||||
|
|
||||||
<div >
|
<div >
|
||||||
<h2 style={{ fontSize: '17px', fontWeight: 700, color: 'var(--text-strong)', marginBottom: '12px', lineHeight: '1.3' }}>
|
<h2 className="text-[17px] font-bold text-strong mb-3 leading-snug">
|
||||||
{storeName}
|
{storeName}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'max-content 1fr', columnGap: '12px', rowGap: '8px', fontSize: '13px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'max-content 1fr', columnGap: '12px', rowGap: '8px', fontSize: '13px' }}>
|
||||||
{ownerName !== '—' && (
|
{ownerName !== '—' && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', color: 'var(--text-muted)' }}>
|
<div className="flex items-center gap-1.5 text-muted">
|
||||||
<UserIcon size={14} />
|
<UserIcon size={14} />
|
||||||
<span>Owner</span>
|
<span>Owner</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontWeight: 500, color: 'var(--text-strong)' }}>{ownerName}</div>
|
<div className="font-medium text-strong">{ownerName}</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{areaName !== '—' && (
|
{areaName !== '—' && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', color: 'var(--text-muted)' }}>
|
<div className="flex items-center gap-1.5 text-muted">
|
||||||
<MapPin size={14} />
|
<MapPin size={14} />
|
||||||
<span>Area</span>
|
<span>Area</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontWeight: 500, color: 'var(--text-strong)' }}>{areaName}</div>
|
<div className="font-medium text-strong">{areaName}</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -135,22 +135,22 @@ export function StoreCard({ row, isDetailView = false, onEdit }: { row: Record<s
|
|||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
<div style={{ width: '32px', height: '32px', borderRadius: '8px', background: 'var(--surface-sunk)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)' }}>
|
<div className="w-8 h-8 rounded-lg bg-sunk flex items-center justify-center text-muted">
|
||||||
<Phone size={16} />
|
<Phone size={16} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<span style={{ fontSize: '11px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Phone</span>
|
<span className="text-[11px] font-semibold text-muted uppercase tracking-wider">Phone</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 500, color: 'var(--text-strong)' }}>{phone}</span>
|
<span className="text-sm font-medium text-strong">{phone}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
<div style={{ width: '32px', height: '32px', borderRadius: '8px', background: 'var(--surface-sunk)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)' }}>
|
<div className="w-8 h-8 rounded-lg bg-sunk flex items-center justify-center text-muted">
|
||||||
<Mail size={16} />
|
<Mail size={16} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<span style={{ fontSize: '11px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Email</span>
|
<span className="text-[11px] font-semibold text-muted uppercase tracking-wider">Email</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 500, color: 'var(--text-strong)' }}>{email}</span>
|
<span className="text-sm font-medium text-strong">{email}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -406,21 +406,21 @@ export function CallDetail({
|
|||||||
|
|
||||||
{/* Top Right KPI Grid */}
|
{/* Top Right KPI Grid */}
|
||||||
<div className="flex flex-nowrap items-center gap-3 w-full lg:w-auto overflow-x-auto pb-2 min-w-0">
|
<div className="flex flex-nowrap items-center gap-3 w-full lg:w-auto overflow-x-auto pb-2 min-w-0">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center', minWidth: '100px', flex: 1 }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-3 text-center min-w-[100px] flex-1">
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>TOTAL BAGS</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">TOTAL BAGS</div>
|
||||||
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{totalBags.toLocaleString()}</div>
|
<div className="text-[20px] font-extrabold text-primary">{totalBags.toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center', minWidth: '100px', flex: 1 }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-3 text-center min-w-[100px] flex-1">
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>TOTAL KGS</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">TOTAL KGS</div>
|
||||||
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{totalKgs.toLocaleString()}</div>
|
<div className="text-[20px] font-extrabold text-primary">{totalKgs.toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center', minWidth: '100px', flex: 1 }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-3 text-center min-w-[100px] flex-1">
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>LINE ITEMS</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">LINE ITEMS</div>
|
||||||
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{lineItemsCount}</div>
|
<div className="text-[20px] font-extrabold text-primary">{lineItemsCount}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center', minWidth: '110px', flex: 1 }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-3 text-center min-w-[110px] flex-1">
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>SALES OFFICER</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">SALES OFFICER</div>
|
||||||
<div style={{ fontSize: '14px', fontWeight: 700, color: 'var(--primary-color)', marginTop: '4px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{salesOfficer}</div>
|
<div className="text-sm font-bold text-primary mt-1 whitespace-nowrap overflow-hidden text-ellipsis">{salesOfficer}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -520,8 +520,8 @@ export function CallDetail({
|
|||||||
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between border-b border-slate-100 pb-3 gap-3">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between border-b border-slate-100 pb-3 gap-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', padding: '6px', borderRadius: '50%', border: '1px solid #bcecd0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
|
||||||
<ShoppingCart size={14} color="var(--primary-color)" />
|
<ShoppingCart size={14} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Order Details</h2>
|
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Order Details</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -541,44 +541,36 @@ export function CallDetail({
|
|||||||
const bagsStr = item.bags;
|
const bagsStr = item.bags;
|
||||||
const kgsStr = item.totalKgs > 0 ? `${item.totalKgs.toLocaleString()} kg` : '0 kg';
|
const kgsStr = item.totalKgs > 0 ? `${item.totalKgs.toLocaleString()} kg` : '0 kg';
|
||||||
return (
|
return (
|
||||||
<div key={idx} style={{
|
<div key={idx} className="flex justify-between items-center bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-3.5">
|
||||||
display: 'flex',
|
<div className="flex flex-col gap-1">
|
||||||
justifyContent: 'space-between',
|
<span className="text-primary font-semibold text-sm">{item.name}</span>
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: '#e8fbf0',
|
|
||||||
border: '1px solid #bcecd0',
|
|
||||||
borderRadius: '24px',
|
|
||||||
padding: '14px 20px',
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '14px' }}>{item.name}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
|
<div className="text-right whitespace-nowrap">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '14px' }}>{bagsStr} Bags</span>
|
<span className="text-primary font-bold text-sm">{bagsStr} Bags</span>
|
||||||
<span style={{ color: 'var(--primary-color)', fontSize: '13px', marginLeft: '6px' }}>({kgsStr})</span>
|
<span className="text-primary text-[13px] ml-1.5">({kgsStr})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 rounded-xl p-4 flex items-center justify-between shadow-sm" style={{ backgroundColor: 'var(--primary-color)', color: '#ffffff' }}>
|
<div className="mt-2 rounded-xl p-4 flex items-center justify-between shadow-sm bg-primary text-white">
|
||||||
<span className="text-xs font-bold uppercase tracking-wider" style={{ opacity: 0.9 }}>Total Order</span>
|
<span className="text-xs font-bold uppercase tracking-wider opacity-90">Total Order</span>
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex flex-col items-end">
|
<div className="flex flex-col items-end">
|
||||||
<span className="text-[10px] font-medium uppercase" style={{ opacity: 0.8 }}>Bags</span>
|
<span className="text-[10px] font-medium uppercase opacity-80">Bags</span>
|
||||||
<span className="text-lg font-black">{totalBags}</span>
|
<span className="text-lg font-black">{totalBags}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-end">
|
<div className="flex flex-col items-end">
|
||||||
<span className="text-[10px] font-medium uppercase" style={{ opacity: 0.8 }}>Total Kgs</span>
|
<span className="text-[10px] font-medium uppercase opacity-80">Total Kgs</span>
|
||||||
<span className="text-lg font-black text-emerald-300">{totalKgs.toLocaleString()}</span>
|
<span className="text-lg font-black text-emerald-300">{totalKgs.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '12px', padding: '32px 24px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', gap: '8px' }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-xl px-6 py-8 flex flex-col items-center justify-center text-center gap-2">
|
||||||
<ShoppingCart size={24} color="var(--primary-color)" style={{ opacity: 0.5 }} />
|
<ShoppingCart size={24} color="currentColor" className="text-primary opacity-50" />
|
||||||
<span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--primary-color)' }}>Order not placed</span>
|
<span className="text-[13px] font-semibold text-primary">Order not placed</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -604,8 +596,8 @@ export function CallDetail({
|
|||||||
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
||||||
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', padding: '6px', borderRadius: '50%', border: '1px solid #bcecd0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
|
||||||
<TrendingUp size={14} color="var(--primary-color)" />
|
<TrendingUp size={14} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Potential vs Ordered</h2>
|
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Potential vs Ordered</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -619,42 +611,34 @@ export function CallDetail({
|
|||||||
const renderTable = (items: any[]) => (
|
const renderTable = (items: any[]) => (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{items.map((item, idx) => (
|
{items.map((item, idx) => (
|
||||||
<div key={idx} style={{
|
<div key={idx} className="bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-4 flex flex-col gap-3">
|
||||||
backgroundColor: '#e8fbf0',
|
|
||||||
border: '1px solid #bcecd0',
|
|
||||||
borderRadius: '24px',
|
|
||||||
padding: '16px 20px',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '12px'
|
|
||||||
}}>
|
|
||||||
<div className="flex justify-between items-start gap-4">
|
<div className="flex justify-between items-start gap-4">
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '15px' }}>{item.name}</span>
|
<span className="text-primary font-semibold text-[15px]">{item.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#ffffff', borderRadius: '16px', padding: '12px 16px', border: '1px solid #bcecd0' }}>
|
<div className="flex justify-between items-center bg-white rounded-2xl py-3 px-4 border border-primary-light-border">
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Potential</span>
|
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Potential</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 600, color: 'var(--primary-color)' }}>{item.actualPotential}</span>
|
<span className="text-sm font-semibold text-primary">{item.actualPotential}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Ordered</span>
|
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Ordered</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 600, color: 'var(--primary-color)' }}>{item.totalOrdered}</span>
|
<span className="text-sm font-semibold text-primary">{item.totalOrdered}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Diff</span>
|
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Diff</span>
|
||||||
<span style={{ fontSize: '15px', fontWeight: 900, color: item.difference >= 0 ? '#059669' : '#ef4444' }}>
|
<span className={`text-[15px] font-black ${item.difference >= 0 ? 'text-emerald-600' : 'text-red-500'}`}>
|
||||||
{item.difference > 0 ? '+' : ''}{item.difference}
|
{item.difference > 0 ? '+' : ''}{item.difference}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{item.reason && (
|
{item.reason && (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', borderRadius: '12px', border: '1px solid #fecaca', backgroundColor: '#fef2f2', marginTop: '4px' }}>
|
<div className="flex items-center justify-between px-3 py-2 rounded-xl border border-red-200 bg-red-50 mt-1">
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: '#ef4444', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Reason</span>
|
<span className="text-[10px] font-bold text-red-500 uppercase tracking-wider">Reason</span>
|
||||||
<span style={{ fontSize: '11px', fontWeight: 700, color: '#dc2626' }}>{item.reason}</span>
|
<span className="text-[11px] font-bold text-red-600">{item.reason}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -747,9 +731,9 @@ export function CallDetail({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '12px', padding: '24px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', gap: '8px' }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-xl p-6 flex flex-col items-center justify-center text-center gap-2">
|
||||||
<Camera size={24} color="var(--primary-color)" />
|
<Camera size={24} color="currentColor" className="text-primary" />
|
||||||
<span style={{ fontSize: '12px', fontWeight: 600, color: 'var(--primary-color)' }}>No proof image uploaded</span>
|
<span className="text-xs font-semibold text-primary">No proof image uploaded</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -775,11 +759,11 @@ export function CallDetail({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ backgroundColor: '#e8fbf0', borderBottom: '1px solid #bcecd0', padding: '24px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', height: '160px', textAlign: 'center' }}>
|
<div className="bg-primary-light-bg border-b border-primary-light-border p-6 flex flex-col items-center justify-center gap-2 h-[160px] text-center">
|
||||||
<div style={{ width: '48px', height: '48px', borderRadius: '50%', backgroundColor: '#ffffff', border: '1px solid #bcecd0', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)' }}>
|
<div className="w-12 h-12 rounded-full bg-white border border-primary-light-border flex items-center justify-center shadow-sm">
|
||||||
<ImageIcon size={22} color="var(--primary-color)" />
|
<ImageIcon size={22} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<span style={{ fontSize: '12px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>{storeName}</span>
|
<span className="text-xs font-bold text-primary uppercase tracking-wider">{storeName}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -788,8 +772,8 @@ export function CallDetail({
|
|||||||
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
||||||
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', padding: '6px', borderRadius: '50%', border: '1px solid #bcecd0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
|
||||||
<Store size={14} color="var(--primary-color)" />
|
<Store size={14} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Store</h2>
|
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Store</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -840,11 +824,11 @@ export function CallDetail({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Notes Container */}
|
{/* Notes Container */}
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '12px', padding: '14px', gap: '6px', display: 'flex', flexDirection: 'column' }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-xl p-3.5 gap-1.5 flex flex-col">
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div className="text-[11px] font-bold text-primary uppercase tracking-wider flex items-center gap-1.5">
|
||||||
<FileText size={12} color="var(--primary-color)" /> NOTES
|
<FileText size={12} color="currentColor" className="text-primary" /> NOTES
|
||||||
</div>
|
</div>
|
||||||
<p style={{ color: 'var(--primary-color)', lineHeight: 1.5, fontWeight: 500, margin: 0, fontSize: '12px' }}>
|
<p className="text-primary leading-relaxed font-medium m-0 text-xs">
|
||||||
{storeNotes}
|
{storeNotes}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -855,8 +839,8 @@ export function CallDetail({
|
|||||||
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
||||||
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', padding: '6px', borderRadius: '50%', border: '1px solid #bcecd0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
|
||||||
<TrendingUp size={14} color="var(--primary-color)" />
|
<TrendingUp size={14} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Store Potential</h2>
|
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Store Potential</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -865,20 +849,12 @@ export function CallDetail({
|
|||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{storePotentialList.map((item, idx) => (
|
{storePotentialList.map((item, idx) => (
|
||||||
<div key={idx} style={{
|
<div key={idx} className="flex justify-between items-center bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-3.5">
|
||||||
display: 'flex',
|
<div className="flex flex-col gap-1">
|
||||||
justifyContent: 'space-between',
|
<span className="text-primary font-semibold text-sm">{item.name}</span>
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: '#e8fbf0',
|
|
||||||
border: '1px solid #bcecd0',
|
|
||||||
borderRadius: '24px',
|
|
||||||
padding: '14px 20px',
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '14px' }}>{item.name}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
|
<div className="text-right whitespace-nowrap">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '14px' }}>{item.actualPotential} KG Potential</span>
|
<span className="text-primary font-bold text-sm">{item.actualPotential} KG Potential</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -889,8 +865,8 @@ export function CallDetail({
|
|||||||
{/* Distributor Card */}
|
{/* Distributor Card */}
|
||||||
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
<div className="bg-[var(--tiles-card-bg)] rounded-2xl border border-slate-200/80 p-6 shadow-sm space-y-4">
|
||||||
<div className="flex items-center gap-2 border-b border-slate-100 pb-3">
|
<div className="flex items-center gap-2 border-b border-slate-100 pb-3">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', padding: '6px', borderRadius: '50%', border: '1px solid #bcecd0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div className="bg-primary-light-bg p-1.5 rounded-full border border-primary-light-border flex items-center justify-center">
|
||||||
<Truck size={14} color="var(--primary-color)" />
|
<Truck size={14} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Distributor</h2>
|
<h2 className="text-sm font-bold text-slate-900 uppercase tracking-wide">Distributor</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ORDER_BOOKING } from '../../api/config';
|
|||||||
import { Card } from '../reusable/Card';
|
import { Card } from '../reusable/Card';
|
||||||
import { Spinner } from '../reusable/Spinner';
|
import { Spinner } from '../reusable/Spinner';
|
||||||
import { EmptyState } from '../reusable/EmptyState';
|
import { EmptyState } from '../reusable/EmptyState';
|
||||||
import { Store, User, Package, Calendar, Hash, Mail, Weight, List, MapPin, ArrowLeft } from 'lucide-react';
|
import { Store, User, Package, Calendar, Hash, Mail, MapPin, ArrowLeft } from 'lucide-react';
|
||||||
|
|
||||||
export interface WiredDetailViewProps {
|
export interface WiredDetailViewProps {
|
||||||
instanceId: number | string;
|
instanceId: number | string;
|
||||||
@ -157,16 +157,16 @@ export function OrderDetail({ instanceId, onDataLoad, onBack }: WiredDetailViewP
|
|||||||
{/* Middle Stats Section */}
|
{/* Middle Stats Section */}
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
|
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>TOTAL BAGS</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">TOTAL BAGS</div>
|
||||||
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{String(totalBags)}</div>
|
<div className="text-[20px] font-extrabold text-primary">{String(totalBags)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
|
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>TOTAL KGS</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">TOTAL KGS</div>
|
||||||
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{Number(totalKgs).toLocaleString()}</div>
|
<div className="text-[20px] font-extrabold text-primary">{Number(totalKgs).toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
|
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '12px', textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>LINE ITEMS</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">LINE ITEMS</div>
|
||||||
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--primary-color)' }}>{itemsArray.length}</div>
|
<div className="text-[20px] font-extrabold text-primary">{itemsArray.length}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -186,13 +186,13 @@ export function OrderDetail({ instanceId, onDataLoad, onBack }: WiredDetailViewP
|
|||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||||
{itemsArray.map((item: any, idx: number) => {
|
{itemsArray.map((item: any, idx: number) => {
|
||||||
const productName = String(item.product_name_3 || item.product_name || 'Unknown Product');
|
const productName = String(item.product_name_3 || item.product_name || 'Unknown Product');
|
||||||
const skuCodeRaw = item.sku_code_3 || item.sku_code || '';
|
// const skuCodeRaw = item.sku_code_3 || item.sku_code || '';
|
||||||
const skuCode = isNaN(Number(skuCodeRaw)) || String(skuCodeRaw).length > 3 ? String(skuCodeRaw) : '';
|
// const skuCode = isNaN(Number(skuCodeRaw)) || String(skuCodeRaw).length > 3 ? String(skuCodeRaw) : '';
|
||||||
const category = String(item.product_category_3 || item.product_category || '');
|
// const category = String(item.product_category_3 || item.product_category || '');
|
||||||
const brCode = String(item.br_code_3 || item.br_code || '');
|
// const brCode = String(item.br_code_3 || item.br_code || '');
|
||||||
|
|
||||||
const subtitleArr = [skuCode, category, brCode].filter(v => v && v !== '-' && v !== '—');
|
// const subtitleArr = [skuCode, category, brCode].filter(v => v && v !== '-' && v !== '—');
|
||||||
const subtitle = subtitleArr.join(' . ').toUpperCase();
|
// const subtitle = subtitleArr.join(' . ').toUpperCase();
|
||||||
|
|
||||||
const bagsNum = Number(item.bags_3 || item.bags || 0);
|
const bagsNum = Number(item.bags_3 || item.bags || 0);
|
||||||
const bagsStr = String(bagsNum);
|
const bagsStr = String(bagsNum);
|
||||||
@ -203,21 +203,13 @@ export function OrderDetail({ instanceId, onDataLoad, onBack }: WiredDetailViewP
|
|||||||
const kgsStr = kgsNum > 0 ? `${kgsNum} kg` : '0 kg';
|
const kgsStr = kgsNum > 0 ? `${kgsNum} kg` : '0 kg';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={idx} style={{
|
<div key={idx} className="flex justify-between items-center bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-3.5">
|
||||||
display: 'flex',
|
<div className="flex flex-col gap-1">
|
||||||
justifyContent: 'space-between',
|
<span className="text-primary font-semibold text-sm">{productName}</span>
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: '#e8fbf0',
|
|
||||||
border: '1px solid #bcecd0',
|
|
||||||
borderRadius: '24px',
|
|
||||||
padding: '14px 20px',
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '14px' }}>{productName}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
|
<div className="text-right whitespace-nowrap">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '14px' }}>{bagsStr} Bags</span>
|
<span className="text-primary font-bold text-sm">{bagsStr} Bags</span>
|
||||||
<span style={{ color: 'var(--primary-color)', fontSize: '13px', marginLeft: '6px' }}>({kgsStr})</span>
|
<span className="text-primary text-[13px] ml-1.5">({kgsStr})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -236,7 +228,7 @@ export function OrderDetail({ instanceId, onDataLoad, onBack }: WiredDetailViewP
|
|||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '13px' }}>TOTAL</span>
|
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '13px' }}>TOTAL</span>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '15px' }}>{totalBags} Bags</span>
|
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '15px' }}>{totalBags} Bags</span>
|
||||||
<span style={{ color: 'var(--primary-color)', fontSize: '14px', marginLeft: '6px' }}>({Number(totalKgs).toLocaleString()} kg)</span>
|
<span className="text-primary text-sm ml-1.5">({Number(totalKgs).toLocaleString()} kg)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -29,18 +29,10 @@ export function PotentialMiningTable({ data, onEdit, onDelete, hideOrderDetails
|
|||||||
const absDiff = Math.abs(difference);
|
const absDiff = Math.abs(difference);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.id} style={{
|
<div key={item.id} className="bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-4 flex flex-col gap-3">
|
||||||
backgroundColor: '#e8fbf0',
|
|
||||||
border: '1px solid #bcecd0',
|
|
||||||
borderRadius: '24px',
|
|
||||||
padding: '16px 20px',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '12px'
|
|
||||||
}}>
|
|
||||||
<div className="flex justify-between items-start gap-4">
|
<div className="flex justify-between items-start gap-4">
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '15px' }}>{item.productName}</span>
|
<span className="text-primary font-semibold text-[15px]">{item.productName}</span>
|
||||||
{item.badge && (
|
{item.badge && (
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"px-2 py-0.5 rounded-full text-[10px] font-semibold self-start",
|
"px-2 py-0.5 rounded-full text-[10px] font-semibold self-start",
|
||||||
@ -56,20 +48,20 @@ export function PotentialMiningTable({ data, onEdit, onDelete, hideOrderDetails
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#ffffff', borderRadius: '16px', padding: '12px 16px', border: '1px solid #bcecd0' }}>
|
<div className="flex justify-between items-center bg-white rounded-2xl py-3 px-4 border border-primary-light-border">
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Potential</span>
|
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Potential</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 600, color: 'var(--primary-color)' }}>{item.potentialKgs} kg</span>
|
<span className="text-sm font-semibold text-primary">{item.potentialKgs} kg</span>
|
||||||
</div>
|
</div>
|
||||||
{!hideOrderDetails && (
|
{!hideOrderDetails && (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Ordered</span>
|
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Ordered</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 600, color: 'var(--primary-color)' }}>{item.orderedKgs} kg</span>
|
<span className="text-sm font-semibold text-primary">{item.orderedKgs} kg</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
<div className="flex flex-col gap-0.5 border-l border-slate-200 pl-4">
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Diff</span>
|
<span className="text-[10px] font-bold text-primary uppercase tracking-wider">Diff</span>
|
||||||
<span style={{ fontSize: '15px', fontWeight: 900, color: isSurplus ? '#059669' : '#ef4444' }}>
|
<span className={`text-[15px] font-black ${isSurplus ? 'text-emerald-600' : 'text-red-500'}`}>
|
||||||
{isSurplus ? '+' : '-'}{absDiff}
|
{isSurplus ? '+' : '-'}{absDiff}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -83,7 +75,7 @@ export function PotentialMiningTable({ data, onEdit, onDelete, hideOrderDetails
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onEdit(item)}
|
onClick={() => onEdit(item)}
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 12px', backgroundColor: '#e0f2fe', color: '#0284c7', borderRadius: '8px', fontSize: '12px', fontWeight: 600, border: '1px solid #bae6fd' }}
|
className="flex items-center gap-1.5 px-3 py-1.5 bg-sky-100 text-sky-600 rounded-lg text-xs font-semibold border border-sky-200"
|
||||||
>
|
>
|
||||||
<Pencil size={14} /> Edit
|
<Pencil size={14} /> Edit
|
||||||
</button>
|
</button>
|
||||||
@ -92,7 +84,7 @@ export function PotentialMiningTable({ data, onEdit, onDelete, hideOrderDetails
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDelete(item)}
|
onClick={() => onDelete(item)}
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 12px', backgroundColor: '#fee2e2', color: '#dc2626', borderRadius: '8px', fontSize: '12px', fontWeight: 600, border: '1px solid #fecaca' }}
|
className="flex items-center gap-1.5 px-3 py-1.5 bg-red-100 text-red-600 rounded-lg text-xs font-semibold border border-red-200"
|
||||||
>
|
>
|
||||||
<Trash2 size={14} /> Delete
|
<Trash2 size={14} /> Delete
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -190,8 +190,8 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
|
|||||||
<img src={imageUrl} alt={String(businessName)} className="w-full h-full object-cover" />
|
<img src={imageUrl} alt={String(businessName)} className="w-full h-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ backgroundColor: '#e8fbf0', borderRight: '1px solid #bcecd0' }} className="md:w-1/3 lg:w-1/4 h-48 md:h-auto flex items-center justify-center">
|
<div className="bg-primary-light-bg border-r border-primary-light-border md:w-1/3 lg:w-1/4 h-48 md:h-auto flex items-center justify-center">
|
||||||
<Store size={48} color="var(--primary-color)" style={{ opacity: 0.5 }} />
|
<Store size={48} color="currentColor" className="text-primary opacity-50" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -301,19 +301,19 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
|
|||||||
<h3 className="text-xs font-bold text-slate-800 uppercase tracking-wider m-0">Route Assignment</h3>
|
<h3 className="text-xs font-bold text-slate-800 uppercase tracking-wider m-0">Route Assignment</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-5 grid grid-cols-1 sm:grid-cols-3 gap-4 bg-[var(--tiles-card-bg)]">
|
<div className="p-5 grid grid-cols-1 sm:grid-cols-3 gap-4 bg-[var(--tiles-card-bg)]">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '16px', textAlign: 'center' }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-4 text-center">
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>Route</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">Route</div>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 800, color: 'var(--primary-color)' }}>{routeName ? String(routeName) : '-'}</div>
|
<div className="text-base font-extrabold text-primary">{routeName ? String(routeName) : '-'}</div>
|
||||||
<div style={{ fontSize: '12px', color: 'var(--primary-color)', marginTop: '4px' }}>Code {routeCode ? String(routeCode) : '-'}</div>
|
<div className="text-xs text-primary mt-1">Code {routeCode ? String(routeCode) : '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '16px', textAlign: 'center' }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-4 text-center">
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>Sub Route</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">Sub Route</div>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 800, color: 'var(--primary-color)' }}>{subRoute ? String(subRoute) : '-'}</div>
|
<div className="text-base font-extrabold text-primary">{subRoute ? String(subRoute) : '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '16px', textAlign: 'center' }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-4 text-center">
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '2px' }}>Area</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">Area</div>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 800, color: 'var(--primary-color)' }}>{area ? String(area) : '-'}</div>
|
<div className="text-base font-extrabold text-primary">{area ? String(area) : '-'}</div>
|
||||||
<div style={{ fontSize: '12px', color: 'var(--primary-color)', marginTop: '4px' }}>PIN {pinCode ? String(pinCode) : '-'}</div>
|
<div className="text-xs text-primary mt-1">PIN {pinCode ? String(pinCode) : '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@ -330,27 +330,19 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
|
|||||||
{potential.map((p: any, idx: number) => {
|
{potential.map((p: any, idx: number) => {
|
||||||
if (!p.product_category && !p.quantity) return null;
|
if (!p.product_category && !p.quantity) return null;
|
||||||
return (
|
return (
|
||||||
<div key={idx} style={{
|
<div key={idx} className="flex justify-between items-center bg-primary-light-bg border border-primary-light-border rounded-3xl px-5 py-3.5">
|
||||||
display: 'flex',
|
<div className="flex flex-col gap-1">
|
||||||
justifyContent: 'space-between',
|
<span className="text-primary font-semibold text-sm">{p.product_category ? String(p.product_category) : '-'}</span>
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: '#e8fbf0',
|
|
||||||
border: '1px solid #bcecd0',
|
|
||||||
borderRadius: '24px',
|
|
||||||
padding: '14px 20px',
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 600, fontSize: '14px' }}>{p.product_category ? String(p.product_category) : '-'}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
|
<div className="text-right whitespace-nowrap">
|
||||||
<span style={{ color: 'var(--primary-color)', fontWeight: 700, fontSize: '14px' }}>{p.quantity ? String(p.quantity) : '0'} Potential</span>
|
<span className="text-primary font-bold text-sm">{p.quantity ? String(p.quantity) : '0'} Potential</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 rounded-xl p-4 flex items-center justify-between shadow-sm" style={{ backgroundColor: 'var(--primary-color)', color: '#ffffff' }}>
|
<div className="mt-4 rounded-xl p-4 flex items-center justify-between shadow-sm bg-primary text-white">
|
||||||
<span className="text-xs font-bold uppercase tracking-wider" style={{ opacity: 0.9 }}>Total Potential</span>
|
<span className="text-xs font-bold uppercase tracking-wider opacity-90">Total Potential</span>
|
||||||
<span className="text-lg font-black text-emerald-300">{totalPotential}</span>
|
<span className="text-lg font-black text-emerald-300">{totalPotential}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -364,27 +356,27 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
|
|||||||
<h3 className="text-xs font-bold text-slate-800 uppercase tracking-wider m-0">Distributor</h3>
|
<h3 className="text-xs font-bold text-slate-800 uppercase tracking-wider m-0">Distributor</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-5 bg-[var(--tiles-card-bg)]">
|
<div className="p-5 bg-[var(--tiles-card-bg)]">
|
||||||
<div style={{ backgroundColor: '#e8fbf0', border: '1px solid #bcecd0', borderRadius: '16px', padding: '16px' }}>
|
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-4">
|
||||||
<div style={{ fontSize: '14px', fontWeight: 800, color: 'var(--primary-color)', marginBottom: '4px' }}>{distributorName ? String(distributorName) : '-'}</div>
|
<div className="text-sm font-extrabold text-primary mb-1">{distributorName ? String(distributorName) : '-'}</div>
|
||||||
<div style={{ fontSize: '12px', color: 'var(--primary-color)', marginBottom: '16px' }}>Owner · {distributorOwnerName ? String(distributorOwnerName) : '-'}</div>
|
<div className="text-xs text-primary mb-4">Owner · {distributorOwnerName ? String(distributorOwnerName) : '-'}</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-8">
|
<div className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-8">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div style={{ width: '32px', height: '32px', borderRadius: '50%', backgroundColor: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid #bcecd0' }}>
|
<div className="w-8 h-8 rounded-full bg-white flex items-center justify-center border border-primary-light-border">
|
||||||
<Phone size={14} color="var(--primary-color)" />
|
<Phone size={14} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Phone</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider">Phone</div>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--primary-color)' }}>{String(distributorPhone)}</div>
|
<div className="text-xs font-semibold text-primary">{String(distributorPhone)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div style={{ width: '32px', height: '32px', borderRadius: '50%', backgroundColor: '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid #bcecd0' }}>
|
<div className="w-8 h-8 rounded-full bg-white flex items-center justify-center border border-primary-light-border">
|
||||||
<Mail size={14} color="var(--primary-color)" />
|
<Mail size={14} color="currentColor" className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--primary-color)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Email</div>
|
<div className="text-[10px] font-bold text-primary uppercase tracking-wider">Email</div>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--primary-color)' }}>{distributorEmail ? String(distributorEmail) : '-'}</div>
|
<div className="text-xs font-semibold text-primary">{distributorEmail ? String(distributorEmail) : '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -204,6 +204,77 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
// Auto-calculate order_details totals
|
// Auto-calculate order_details totals
|
||||||
const getBaseIdForField = (id: string) => id.replace(/_\d+$/, '');
|
const getBaseIdForField = (id: string) => id.replace(/_\d+$/, '');
|
||||||
const fieldDef = schema?.fields.find(f => f.id === fieldId);
|
const fieldDef = schema?.fields.find(f => f.id === fieldId);
|
||||||
|
|
||||||
|
// Auto-fill dataset keys from _raw for select/multiselect fields
|
||||||
|
if (fieldDef && (fieldDef.data_type === 'select' || fieldDef.data_type === 'multiselect') && fieldDef.properties?.options) {
|
||||||
|
const option = fieldDef.properties.options.find(o => String(o.value) === String(newVal));
|
||||||
|
if (option && option._raw) {
|
||||||
|
const rawData = option._raw;
|
||||||
|
const getBaseId = (id: string) => id.replace(/_\d+$/, '');
|
||||||
|
const baseFieldId = getBaseId(fieldId);
|
||||||
|
const prefixMatch = baseFieldId.match(/^([a-z]+)_/);
|
||||||
|
const prefix = prefixMatch ? prefixMatch[1] + '_' : '';
|
||||||
|
|
||||||
|
for (const key of Object.keys(rawData)) {
|
||||||
|
const lowerKey = key.toLowerCase();
|
||||||
|
let targetCol = schema?.fields.find(c =>
|
||||||
|
c.id.toLowerCase() === lowerKey ||
|
||||||
|
(c.mapped_workflow_field || '').toLowerCase() === lowerKey ||
|
||||||
|
getBaseId(c.id).toLowerCase() === lowerKey ||
|
||||||
|
c.name.toLowerCase().replace(/\s+/g, '') === lowerKey.replace(/_/g, '')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fieldId.startsWith('route_name') && targetCol) {
|
||||||
|
const baseTargetId = getBaseId(targetCol.id).toLowerCase();
|
||||||
|
if (baseTargetId === 'owner_name' || baseTargetId === 'phone' || baseTargetId === 'phone_number' || baseTargetId === 'email') {
|
||||||
|
targetCol = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetCol && (fieldId === 'route_name_2' || fieldId.startsWith('route_name'))) {
|
||||||
|
targetCol = schema?.fields.find(c => {
|
||||||
|
const mappedName = (c.mapped_workflow_field || '').toLowerCase();
|
||||||
|
const nameFallback = (c.name || '').toLowerCase().replace(/\s+/g, '_');
|
||||||
|
const fieldName = mappedName || nameFallback;
|
||||||
|
|
||||||
|
return fieldName === `distributor_${lowerKey}` ||
|
||||||
|
(lowerKey === 'phone' && fieldName === 'distributor_phone_number') ||
|
||||||
|
(lowerKey === 'email' && fieldName === 'distributor_email');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetCol) {
|
||||||
|
const pKey = prefix + lowerKey;
|
||||||
|
targetCol = schema?.fields.find(c =>
|
||||||
|
c.id.toLowerCase() === pKey ||
|
||||||
|
(c.mapped_workflow_field || '').toLowerCase() === pKey ||
|
||||||
|
getBaseId(c.id).toLowerCase() === pKey ||
|
||||||
|
c.name.toLowerCase().replace(/\s+/g, '') === pKey.replace(/_/g, '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetCol && targetCol.id !== fieldId) {
|
||||||
|
let fillVal = rawData[key];
|
||||||
|
|
||||||
|
if ((targetCol.data_type === 'select' || targetCol.data_type === 'multiselect') && targetCol.properties?.options) {
|
||||||
|
const matchedOpt = targetCol.properties.options.find(
|
||||||
|
(o: any) => String(o.value).toLowerCase() === String(fillVal).toLowerCase()
|
||||||
|
);
|
||||||
|
if (matchedOpt) {
|
||||||
|
fillVal = matchedOpt.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Auto-fill] Mapping _raw key "${key}" to form field "${targetCol.id}" with value:`, fillVal);
|
||||||
|
next[targetCol.id] = fillVal;
|
||||||
|
} else if (!targetCol && key !== fieldId) {
|
||||||
|
console.log(`[Auto-fill] Mapping _raw key "${key}" directly to form state with value:`, rawData[key]);
|
||||||
|
next[key] = rawData[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isOrderDetails = fieldId === 'order_details' || getBaseIdForField(fieldId) === 'order_details' || fieldDef?.mapped_workflow_field === 'order_details';
|
const isOrderDetails = fieldId === 'order_details' || getBaseIdForField(fieldId) === 'order_details' || fieldDef?.mapped_workflow_field === 'order_details';
|
||||||
|
|
||||||
if (isOrderDetails && Array.isArray(newVal)) {
|
if (isOrderDetails && Array.isArray(newVal)) {
|
||||||
@ -485,6 +556,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
columns={f.columns || []}
|
columns={f.columns || []}
|
||||||
value={(val as Record<string, unknown>[]) || []}
|
value={(val as Record<string, unknown>[]) || []}
|
||||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||||
|
isActivityPotentialMining={currentActivityId === ORDER_BOOKING.activities.POTENTIAL_MINING.uid}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,19 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { Modal } from '../../reusable/Modal';
|
||||||
import { Button } from '../../buttons/Button';
|
import { Button } from '../../buttons/Button';
|
||||||
|
import { useJsApiLoader, GoogleMap, Marker } from '@react-google-maps/api';
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
|
const mapContainerStyle = {
|
||||||
|
width: '100%',
|
||||||
|
height: '400px',
|
||||||
|
borderRadius: '8px'
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultCenter = {
|
||||||
|
lat: 20.5937,
|
||||||
|
lng: 78.9629
|
||||||
|
};
|
||||||
|
|
||||||
export function GeolocationInput({
|
export function GeolocationInput({
|
||||||
label,
|
label,
|
||||||
@ -9,12 +23,30 @@ export function GeolocationInput({
|
|||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
value: { latitude: number; longitude: number } | null;
|
value: { latitude: number; longitude: number; accuracy?: number } | null;
|
||||||
onChange: (val: { latitude: number; longitude: number } | null) => void;
|
onChange: (val: { latitude: number; longitude: number; accuracy?: number } | null) => void;
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [isMapModalOpen, setIsMapModalOpen] = useState(false);
|
||||||
|
const [mapMarkerPos, setMapMarkerPos] = useState<{lat: number, lng: number} | null>(null);
|
||||||
|
|
||||||
|
const isLocalhost = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
|
||||||
|
|
||||||
|
const { isLoaded } = useJsApiLoader({
|
||||||
|
id: 'google-map-script',
|
||||||
|
googleMapsApiKey: isLocalhost ? '' : (import.meta.env.VITE_GOOGLE_MAPS_API_KEY || '')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-fetch location on first mount if no value is present
|
||||||
|
useEffect(() => {
|
||||||
|
if (!value) {
|
||||||
|
fetchLocation();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
const fetchLocation = () => {
|
const fetchLocation = () => {
|
||||||
if (!navigator.geolocation) {
|
if (!navigator.geolocation) {
|
||||||
setError('Geolocation is not supported by your browser.');
|
setError('Geolocation is not supported by your browser.');
|
||||||
@ -27,6 +59,7 @@ export function GeolocationInput({
|
|||||||
onChange({
|
onChange({
|
||||||
latitude: position.coords.latitude,
|
latitude: position.coords.latitude,
|
||||||
longitude: position.coords.longitude,
|
longitude: position.coords.longitude,
|
||||||
|
accuracy: position.coords.accuracy,
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
},
|
},
|
||||||
@ -38,24 +71,120 @@ export function GeolocationInput({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenMap = () => {
|
||||||
|
if (value) {
|
||||||
|
setMapMarkerPos({ lat: value.latitude, lng: value.longitude });
|
||||||
|
} else {
|
||||||
|
setMapMarkerPos(null);
|
||||||
|
}
|
||||||
|
setIsMapModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMapClick = useCallback((e: google.maps.MapMouseEvent) => {
|
||||||
|
if (e.latLng) {
|
||||||
|
setMapMarkerPos({
|
||||||
|
lat: e.latLng.lat(),
|
||||||
|
lng: e.latLng.lng()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const confirmMapSelection = () => {
|
||||||
|
if (mapMarkerPos) {
|
||||||
|
onChange({
|
||||||
|
latitude: mapMarkerPos.lat,
|
||||||
|
longitude: mapMarkerPos.lng
|
||||||
|
});
|
||||||
|
setIsMapModalOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = () => onChange(null);
|
||||||
|
const hasValue = !!value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1.5 font-sans">
|
<div className="flex flex-col gap-1.5 font-sans w-full">
|
||||||
<span className="text-sm font-medium text-muted">
|
<span className="text-sm font-medium text-muted">
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="text-ruby-600"> *</span>}
|
{required && <span className="text-ruby-600"> *</span>}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-3 items-start sm:items-center w-full border border-slate-200 bg-white rounded-lg p-2 shadow-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<Button type="button" variant="secondary" size="sm" onClick={fetchLocation} disabled={loading}>
|
<Button type="button" variant="secondary" size="sm" onClick={fetchLocation} disabled={loading}>
|
||||||
{loading ? 'Fetching...' : 'Get Location'}
|
{loading ? 'Locating...' : 'Get Location'}
|
||||||
</Button>
|
</Button>
|
||||||
{value && (
|
<Button type="button" variant="outline" size="sm" onClick={handleOpenMap}>
|
||||||
<span className="text-sm text-strong">
|
Select Map
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasValue ? (
|
||||||
|
<div className="flex items-center gap-2 sm:ml-auto text-sm text-slate-700 font-medium bg-slate-50 px-3 py-1.5 rounded-md border border-slate-100">
|
||||||
|
<span className="flex h-2 w-2 rounded-full bg-emerald-500"></span>
|
||||||
{value.latitude.toFixed(5)}, {value.longitude.toFixed(5)}
|
{value.latitude.toFixed(5)}, {value.longitude.toFixed(5)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={clear}
|
||||||
|
className="ml-1 text-slate-400 hover:text-ruby-500 transition-colors"
|
||||||
|
aria-label="Clear location"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-slate-400 sm:ml-auto px-2 py-1">
|
||||||
|
No location set
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{error && <span className="text-xs text-ruby-600">{error}</span>}
|
|
||||||
|
{error && <span className="text-xs text-ruby-600 px-1">{error}</span>}
|
||||||
{required && !value && <input type="text" className="sr-only" required />}
|
{required && !value && <input type="text" className="sr-only" required />}
|
||||||
|
|
||||||
|
{isMapModalOpen && (
|
||||||
|
<Modal
|
||||||
|
open={isMapModalOpen}
|
||||||
|
onClose={() => setIsMapModalOpen(false)}
|
||||||
|
title="Select Location"
|
||||||
|
width="lg"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<p className="text-sm text-slate-600">Click on the map to place a pin at your desired location.</p>
|
||||||
|
|
||||||
|
<div className="border border-slate-200 rounded-lg overflow-hidden relative min-h-[400px] bg-slate-100 flex items-center justify-center">
|
||||||
|
{!isLoaded ? (
|
||||||
|
<span className="text-slate-500 font-medium">Loading Map...</span>
|
||||||
|
) : (
|
||||||
|
<GoogleMap
|
||||||
|
mapContainerStyle={mapContainerStyle}
|
||||||
|
center={mapMarkerPos || defaultCenter}
|
||||||
|
zoom={mapMarkerPos ? 15 : 5}
|
||||||
|
onClick={handleMapClick}
|
||||||
|
options={{
|
||||||
|
streetViewControl: false,
|
||||||
|
mapTypeControl: false,
|
||||||
|
fullscreenControl: false
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{mapMarkerPos && (
|
||||||
|
<Marker position={mapMarkerPos} />
|
||||||
|
)}
|
||||||
|
</GoogleMap>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3 pt-4 border-t border-slate-100">
|
||||||
|
<Button type="button" variant="secondary" onClick={() => setIsMapModalOpen(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="button" variant="primary" onClick={confirmMapSelection} disabled={!mapMarkerPos}>
|
||||||
|
Confirm Location
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,13 +10,15 @@ export function SmartGridField({
|
|||||||
columns,
|
columns,
|
||||||
value = [],
|
value = [],
|
||||||
onChange,
|
onChange,
|
||||||
|
isActivityPotentialMining = false,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
columns: FormScreenField[];
|
columns: FormScreenField[];
|
||||||
value: Record<string, unknown>[];
|
value: Record<string, unknown>[];
|
||||||
onChange: (val: Record<string, unknown>[]) => void;
|
onChange: (val: Record<string, unknown>[]) => void;
|
||||||
|
isActivityPotentialMining?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const isPotentialMining = label.toLowerCase().includes('potential');
|
const isPotentialMining = isActivityPotentialMining;
|
||||||
const formRef = useRef<HTMLDivElement>(null);
|
const formRef = useRef<HTMLDivElement>(null);
|
||||||
const [newRow, setNewRow] = useState<Record<string, unknown>>({});
|
const [newRow, setNewRow] = useState<Record<string, unknown>>({});
|
||||||
const [editingIdx, setEditingIdx] = useState<number | null>(null);
|
const [editingIdx, setEditingIdx] = useState<number | null>(null);
|
||||||
@ -258,7 +260,7 @@ export function SmartGridField({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 font-sans border border-border-default rounded-md p-4 bg-slate-50">
|
<div className="flex flex-col gap-2 font-sans border border-slate-200 rounded-2xl p-4 bg-card shadow-sm mt-2">
|
||||||
<span className="text-sm font-semibold text-strong mb-2">{label}</span>
|
<span className="text-sm font-semibold text-strong mb-2">{label}</span>
|
||||||
|
|
||||||
{value.length === 0 ? (
|
{value.length === 0 ? (
|
||||||
@ -325,7 +327,7 @@ export function SmartGridField({
|
|||||||
<h4 className="text-sm font-semibold text-strong flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-strong flex items-center gap-2">
|
||||||
<Pencil size={16} className="text-blue-600" /> Edit Row
|
<Pencil size={16} className="text-blue-600" /> Edit Row
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-col min-w-0 gap-1.5 p-3 bg-slate-50 border border-slate-100 rounded-lg mb-2">
|
<div className="flex flex-col min-w-0 gap-1.5 p-4 bg-primary-light-bg border border-primary-light-border rounded-[24px] mb-2 shadow-sm">
|
||||||
<span className="font-bold text-slate-800 text-[14px] truncate">{productName}</span>
|
<span className="font-bold text-slate-800 text-[14px] truncate">{productName}</span>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{(!isPotentialMining || hasBagsCol) && (
|
{(!isPotentialMining || hasBagsCol) && (
|
||||||
@ -354,7 +356,7 @@ export function SmartGridField({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={i} className="bg-slate-50 border border-slate-200 rounded-xl p-3 shadow-sm flex items-center justify-between">
|
<div key={i} className="bg-primary-light-bg border border-primary-light-border rounded-[24px] p-4 shadow-sm flex items-center justify-between">
|
||||||
<div className="flex flex-col min-w-0 pr-4 gap-1.5">
|
<div className="flex flex-col min-w-0 pr-4 gap-1.5">
|
||||||
<span className="font-bold text-slate-800 text-[14px] truncate">{productName}</span>
|
<span className="font-bold text-slate-800 text-[14px] truncate">{productName}</span>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
@ -402,7 +404,7 @@ export function SmartGridField({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="overflow-hidden min-h-0">
|
<div className="overflow-hidden min-h-0">
|
||||||
<div className="p-4 border border-border-subtle rounded-xl bg-white shadow-sm">
|
<div className="p-4 border border-border-subtle rounded-2xl bg-card shadow-sm">
|
||||||
<h4 className="text-sm font-semibold text-strong mb-4 flex items-center gap-2">
|
<h4 className="text-sm font-semibold text-strong mb-4 flex items-center gap-2">
|
||||||
Add New Item
|
Add New Item
|
||||||
</h4>
|
</h4>
|
||||||
@ -411,11 +413,8 @@ export function SmartGridField({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 sm:col-span-2 md:col-span-3 mt-2">
|
<div className="flex justify-end gap-2 sm:col-span-2 md:col-span-3 mt-2">
|
||||||
<Button type="button" variant="ghost" onClick={() => { setNewRow({}); }}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button type="button" variant="primary" onClick={submitNewRow}>
|
<Button type="button" variant="primary" onClick={submitNewRow}>
|
||||||
+ Add to Order
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
238
src/components/maps/NearestStoresMap.tsx
Normal file
238
src/components/maps/NearestStoresMap.tsx
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { useJsApiLoader, GoogleMap, Marker, InfoWindow } from '@react-google-maps/api';
|
||||||
|
import { PIPELINE, BASE_URL } from '../../api/config';
|
||||||
|
import { Loader2, MapPin } from 'lucide-react';
|
||||||
|
|
||||||
|
// Interfaces for the API response
|
||||||
|
interface StoreLocation {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
area: string;
|
||||||
|
business_name: string;
|
||||||
|
distance_km: number;
|
||||||
|
location: StoreLocation;
|
||||||
|
route_name: string;
|
||||||
|
store_code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NearestStoresResponse {
|
||||||
|
message: string;
|
||||||
|
response: {
|
||||||
|
count: number;
|
||||||
|
stores: Store[];
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapContainerStyle = {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
minHeight: '400px',
|
||||||
|
borderRadius: '16px'
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultCenter = { lat: 12.9716, lng: 77.5946 };
|
||||||
|
|
||||||
|
export function NearestStoresMap() {
|
||||||
|
const [userLocation, setUserLocation] = useState<{ lat: number, lng: number } | null>(null);
|
||||||
|
const [stores, setStores] = useState<Store[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [selectedStore, setSelectedStore] = useState<Store | null>(null);
|
||||||
|
|
||||||
|
const isLocalhost = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
|
||||||
|
|
||||||
|
const { isLoaded } = useJsApiLoader({
|
||||||
|
id: 'google-map-script',
|
||||||
|
googleMapsApiKey: isLocalhost ? '' : (import.meta.env.VITE_GOOGLE_MAPS_API_KEY || '')
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchNearestStores = async (lat: number, lng: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BASE_URL}${PIPELINE.endpoints.nearestStores}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'accept': 'application/json, text/plain, */*',
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'groupid': '25',
|
||||||
|
'orgid': '57',
|
||||||
|
'templateid': '189',
|
||||||
|
'x-pipeline-version': 'draft'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lng
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch stores: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json() as NearestStoresResponse;
|
||||||
|
if (data.response && data.response.stores) {
|
||||||
|
setStores(data.response.stores);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message || 'An error occurred while fetching stores.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!navigator.geolocation) {
|
||||||
|
setError('Geolocation is not supported by your browser.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => {
|
||||||
|
const lat = position.coords.latitude;
|
||||||
|
const lng = position.coords.longitude;
|
||||||
|
setUserLocation({ lat, lng });
|
||||||
|
fetchNearestStores(lat, lng);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
setError(err.message || 'Failed to get location');
|
||||||
|
setLoading(false);
|
||||||
|
},
|
||||||
|
{ enableHighAccuracy: true }
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onLoad = useCallback(function callback(map: google.maps.Map) {
|
||||||
|
if (userLocation) {
|
||||||
|
const bounds = new window.google.maps.LatLngBounds();
|
||||||
|
bounds.extend(userLocation);
|
||||||
|
stores.forEach(store => {
|
||||||
|
if (store.location) {
|
||||||
|
bounds.extend({ lat: store.location.latitude, lng: store.location.longitude });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (stores.length > 0) {
|
||||||
|
map.fitBounds(bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [userLocation, stores]);
|
||||||
|
|
||||||
|
if (!isLoaded) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-[400px] flex items-center justify-center bg-slate-50 rounded-xl border border-slate-200">
|
||||||
|
<Loader2 className="w-8 h-8 text-primary animate-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full flex flex-col bg-transparent">
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-50 text-red-600 p-3 m-4 rounded-lg text-sm border border-red-100">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading && !userLocation && (
|
||||||
|
<div className="text-sm text-slate-500 flex items-center gap-2 p-4">
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
Locating you...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="w-full h-[50vh] min-h-[350px] relative px-4 pt-4">
|
||||||
|
<GoogleMap
|
||||||
|
mapContainerStyle={mapContainerStyle}
|
||||||
|
center={userLocation || defaultCenter}
|
||||||
|
zoom={userLocation ? 13 : 5}
|
||||||
|
onLoad={onLoad}
|
||||||
|
options={{
|
||||||
|
streetViewControl: false,
|
||||||
|
mapTypeControl: false,
|
||||||
|
fullscreenControl: false
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* User's Current Location Marker */}
|
||||||
|
{userLocation && (
|
||||||
|
<Marker
|
||||||
|
position={userLocation}
|
||||||
|
icon={{
|
||||||
|
url: 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
|
||||||
|
}}
|
||||||
|
title="You are here"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Stores Markers */}
|
||||||
|
{stores.map((store) => (
|
||||||
|
store.location && (
|
||||||
|
<Marker
|
||||||
|
key={store.store_code}
|
||||||
|
position={{ lat: store.location.latitude, lng: store.location.longitude }}
|
||||||
|
onClick={() => setSelectedStore(store)}
|
||||||
|
title={store.business_name}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Info Window for Selected Store */}
|
||||||
|
{selectedStore && selectedStore.location && (
|
||||||
|
<InfoWindow
|
||||||
|
position={{ lat: selectedStore.location.latitude, lng: selectedStore.location.longitude }}
|
||||||
|
onCloseClick={() => setSelectedStore(null)}
|
||||||
|
>
|
||||||
|
<div className="p-1 max-w-[200px]">
|
||||||
|
<h3 className="font-semibold text-sm text-slate-800">{selectedStore.business_name}</h3>
|
||||||
|
<p className="text-xs text-slate-500 mt-1">{selectedStore.area}</p>
|
||||||
|
<div className="flex justify-between items-center mt-2 pt-2 border-t border-slate-100">
|
||||||
|
<span className="text-[10px] bg-primary/10 text-primary px-1.5 py-0.5 rounded">
|
||||||
|
{selectedStore.route_name}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] text-slate-400 font-medium">
|
||||||
|
{selectedStore.distance_km} km
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</InfoWindow>
|
||||||
|
)}
|
||||||
|
</GoogleMap>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-4 mt-4 mb-4 bg-card rounded-2xl shadow-[0_1px_0_0_rgba(0,0,0,0.02),0_20px_40px_-24px_rgba(20,80,60,0.15)] p-5 flex flex-col gap-4">
|
||||||
|
<h3 className="font-bold text-[17px] text-strong flex items-center gap-2">
|
||||||
|
<MapPin className="w-5 h-5 text-primary" />
|
||||||
|
Nearest Stores ({stores.length})
|
||||||
|
</h3>
|
||||||
|
{loading && stores.length === 0 && (
|
||||||
|
<div className="py-4 text-center text-sm text-slate-500">
|
||||||
|
Fetching nearby stores...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!loading && stores.length === 0 && userLocation && (
|
||||||
|
<div className="py-4 text-center text-sm text-slate-500">
|
||||||
|
No stores found nearby.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{stores.map(store => (
|
||||||
|
<div key={store.store_code} className="z-card !p-3 hover:!border-primary/40 cursor-pointer" onClick={() => setSelectedStore(store)}>
|
||||||
|
<div className="flex justify-between items-start mb-1">
|
||||||
|
<span className="font-bold text-[17px] text-strong line-clamp-1">{store.business_name}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted line-clamp-1">{store.area}</p>
|
||||||
|
<div className="flex justify-between items-center mt-3 pt-3 border-t border-slate-200/60">
|
||||||
|
<span className="text-xs font-semibold text-primary uppercase tracking-wider">{store.route_name}</span>
|
||||||
|
<span className="text-xs font-bold text-strong">{store.distance_km} km</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -62,7 +62,7 @@ export function Modal({ open, onClose, title, subtitle, width = 'md', maxHeight
|
|||||||
<div className="shrink-0 flex justify-center pt-2 pb-1 sm:hidden">
|
<div className="shrink-0 flex justify-center pt-2 pb-1 sm:hidden">
|
||||||
<span className="w-9 h-1 rounded-full bg-border-default" />
|
<span className="w-9 h-1 rounded-full bg-border-default" />
|
||||||
</div>
|
</div>
|
||||||
<header className="shrink-0 flex items-start justify-between gap-3 px-5 py-4 border-b border-border-subtle">
|
<header className="shrink-0 flex justify-between items-center gap-3 px-5 py-4 border-b border-border-subtle">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
{title && <h3 className="m-0 text-md font-semibold text-strong truncate">{title}</h3>}
|
{title && <h3 className="m-0 text-md font-semibold text-strong truncate">{title}</h3>}
|
||||||
{subtitle && <div className="text-xs text-faint mt-0.5">{subtitle}</div>}
|
{subtitle && <div className="text-xs text-faint mt-0.5">{subtitle}</div>}
|
||||||
@ -72,9 +72,9 @@ export function Modal({ open, onClose, title, subtitle, width = 'md', maxHeight
|
|||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="shrink-0 -mr-1 -mt-1 p-1.5 rounded-md text-faint hover:text-strong hover:bg-black/5"
|
className="shrink-0 -mr-1 p-1.5 rounded-md text-slate-800 hover:text-black hover:bg-slate-200 transition-colors"
|
||||||
>
|
>
|
||||||
<X size={18} />
|
<X size={22} strokeWidth={3} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@ -174,13 +174,13 @@ export function RecordView({
|
|||||||
setShowFilters(true);
|
setShowFilters(true);
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex items-center justify-center w-11 h-11 rounded-lg transition-colors shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-[#1F4D36]",
|
"relative flex items-center justify-center w-11 h-11 rounded-lg transition-colors shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-primary",
|
||||||
"bg-[#1F4D36] text-white border border-[#1F4D36] hover:opacity-90"
|
"bg-primary text-white border border-primary hover:opacity-90"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SlidersHorizontal size={16} className="text-white" />
|
<SlidersHorizontal size={16} className="text-white" />
|
||||||
{Object.values(activeFilters).filter(Boolean).length > 0 && (
|
{Object.values(activeFilters).filter(Boolean).length > 0 && (
|
||||||
<span className="absolute -top-1 -right-1 bg-white text-[#1F4D36] text-[10px] w-4 h-4 flex items-center justify-center rounded-full font-bold shadow-sm border border-[#1F4D36]">
|
<span className="absolute -top-1 -right-1 bg-white text-primary text-[10px] w-4 h-4 flex items-center justify-center rounded-full font-bold shadow-sm border border-primary">
|
||||||
{Object.values(activeFilters).filter(Boolean).length}
|
{Object.values(activeFilters).filter(Boolean).length}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export function AllDailyLogsPage() {
|
|||||||
{/* Global Floating Action Button for Punch In */}
|
{/* Global Floating Action Button for Punch In */}
|
||||||
<button
|
<button
|
||||||
onClick={() => { setIsCreating(true); setCreateTitle("Punch In"); }}
|
onClick={() => { setIsCreating(true); setCreateTitle("Punch In"); }}
|
||||||
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-[#1F4D36] rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-primary rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
||||||
>
|
>
|
||||||
<ClipboardList size={24} className="text-white" />
|
<ClipboardList size={24} className="text-white" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -168,7 +168,7 @@ export function CallsPage() {
|
|||||||
{/* Global Floating Action Button for Add Call */}
|
{/* Global Floating Action Button for Add Call */}
|
||||||
<button
|
<button
|
||||||
onClick={() => { setIsCreating(true); setCreateTitle("Log Visit"); }}
|
onClick={() => { setIsCreating(true); setCreateTitle("Log Visit"); }}
|
||||||
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-[#1F4D36] rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-primary rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
||||||
>
|
>
|
||||||
<Phone size={20} className="text-white" />
|
<Phone size={20} className="text-white" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { ClipboardList } 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';
|
||||||
|
import { NearestStoresMap } from '../components/maps/NearestStoresMap';
|
||||||
|
|
||||||
export function DailyLogsPage() {
|
export function DailyLogsPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@ -20,7 +21,10 @@ export function DailyLogsPage() {
|
|||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col h-full bg-transparent w-full relative">
|
||||||
|
<div className="flex-1 overflow-y-auto pb-24">
|
||||||
|
<NearestStoresMap />
|
||||||
|
|
||||||
<DailyLogsView
|
<DailyLogsView
|
||||||
hideTiles
|
hideTiles
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
@ -33,11 +37,12 @@ export function DailyLogsPage() {
|
|||||||
setPunchOutTitle("Punch Out");
|
setPunchOutTitle("Punch Out");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Global Floating Action Button for Punch In */}
|
{/* Global Floating Action Button for Punch In */}
|
||||||
<button
|
<button
|
||||||
onClick={() => { setIsCreating(true); setCreateTitle("Punch In"); }}
|
onClick={() => { setIsCreating(true); setCreateTitle("Punch In"); }}
|
||||||
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-[#1F4D36] rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-primary rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
||||||
>
|
>
|
||||||
<ClipboardList size={24} className="text-white" />
|
<ClipboardList size={24} className="text-white" />
|
||||||
</button>
|
</button>
|
||||||
@ -89,6 +94,6 @@ export function DailyLogsPage() {
|
|||||||
>
|
>
|
||||||
{instanceId != null && <DailyLogDetail instanceId={instanceId} />}
|
{instanceId != null && <DailyLogDetail instanceId={instanceId} />}
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
import { Download, Printer } from 'lucide-react';
|
import { Download, Printer } from 'lucide-react';
|
||||||
import jsPDF from 'jspdf';
|
import jsPDF from 'jspdf';
|
||||||
import autoTable from 'jspdf-autotable';
|
import autoTable from 'jspdf-autotable';
|
||||||
|
import { dailyReportsClient } from '../api/clients';
|
||||||
|
|
||||||
export function DailySalesReportPage() {
|
export function DailySalesReportPage() {
|
||||||
const [date, setDate] = useState('');
|
const [date, setDate] = useState('');
|
||||||
@ -134,27 +135,13 @@ export function DailySalesReportPage() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
setReportData(null);
|
setReportData(null);
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://sandbox.getzino.in/api/papi2/daily-sales-report', {
|
const data = await dailyReportsClient.dailySalesReport({
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'TemplateID': '157',
|
|
||||||
'Accept': 'application/json, text/plain, */*',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'sec-ch-ua-platform': '"Android"',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
date,
|
date,
|
||||||
route,
|
route,
|
||||||
distributor,
|
distributor,
|
||||||
so_name: soName,
|
so_name: soName,
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Error: ${res.status} ${res.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
setReportData(data);
|
setReportData(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError((err as Error).message ?? 'Failed to fetch report');
|
setError((err as Error).message ?? 'Failed to fetch report');
|
||||||
|
|||||||
@ -169,7 +169,7 @@ export function OrdersPage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => { setIsCreating(true); setCreateTitle("Place Order"); }}
|
onClick={() => { setIsCreating(true); setCreateTitle("Place Order"); }}
|
||||||
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-[#1F4D36] rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-primary rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
||||||
>
|
>
|
||||||
<ShoppingBag size={24} className="text-white" />
|
<ShoppingBag size={24} className="text-white" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -52,7 +52,8 @@ export function StoresPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col h-full bg-transparent w-full relative">
|
||||||
|
<div className="flex-1 overflow-y-auto pb-24">
|
||||||
<StoresView
|
<StoresView
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
onRowClick={(row) => {
|
onRowClick={(row) => {
|
||||||
@ -64,11 +65,12 @@ export function StoresPage() {
|
|||||||
setEditTitle("Edit Store");
|
setEditTitle("Edit Store");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Global Floating Action Button for Create Store */}
|
{/* Global Floating Action Button for Create Store */}
|
||||||
<button
|
<button
|
||||||
onClick={() => { setIsCreating(true); setCreateTitle("Create Store"); }}
|
onClick={() => { setIsCreating(true); setCreateTitle("Create Store"); }}
|
||||||
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-[#1F4D36] rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-primary rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
||||||
>
|
>
|
||||||
<Store size={24} className="text-white" />
|
<Store size={24} className="text-white" />
|
||||||
</button>
|
</button>
|
||||||
@ -111,6 +113,6 @@ export function StoresPage() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,6 +115,17 @@
|
|||||||
--text-4xl: var(--text-4xl);
|
--text-4xl: var(--text-4xl);
|
||||||
--text-5xl: var(--text-5xl);
|
--text-5xl: var(--text-5xl);
|
||||||
--text-6xl: var(--text-6xl);
|
--text-6xl: var(--text-6xl);
|
||||||
|
|
||||||
|
/* ---- Custom App Colors ---- */
|
||||||
|
--color-primary: var(--primary-color);
|
||||||
|
--color-primary-light-bg: #e8fbf0;
|
||||||
|
--color-primary-light-border: #bcecd0;
|
||||||
|
|
||||||
|
/* Custom Mint and Ink for Location UI */
|
||||||
|
--color-mint: #dcfce7;
|
||||||
|
--color-mint-foreground: #065f46;
|
||||||
|
--teal-ink: #064e3b;
|
||||||
|
--violet-ink: #6d28d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user