added map

This commit is contained in:
suryacp23 2026-07-28 11:46:02 +05:30
parent ea0aa8ae58
commit 7d6f1a9021
26 changed files with 768 additions and 273 deletions

1
.gitignore vendored
View File

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

91
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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`

View File

@ -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'
}
};

View File

@ -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> = {

View File

@ -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' }}>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
)} )}

View File

@ -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>

View File

@ -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>

View File

@ -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}
/> />
); );
} }

View File

@ -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,11 +23,29 @@ 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) {
@ -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">
<Button type="button" variant="secondary" size="sm" onClick={fetchLocation} disabled={loading}> <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">
{loading ? 'Fetching...' : 'Get Location'} <div className="flex items-center gap-2">
</Button> <Button type="button" variant="secondary" size="sm" onClick={fetchLocation} disabled={loading}>
{value && ( {loading ? 'Locating...' : 'Get Location'}
<span className="text-sm text-strong"> </Button>
<Button type="button" variant="outline" size="sm" onClick={handleOpenMap}>
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>
); );
} }

View File

@ -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>

View 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>
);
}

View File

@ -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>

View File

@ -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>
)} )}

View File

@ -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>

View File

@ -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>

View File

@ -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,8 +21,11 @@ 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">
<DailyLogsView <div className="flex-1 overflow-y-auto pb-24">
<NearestStoresMap />
<DailyLogsView
hideTiles hideTiles
refreshKey={refreshKey} refreshKey={refreshKey}
onRowClick={(row) => { onRowClick={(row) => {
@ -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>
); );
} }

View File

@ -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', date,
headers: { route,
'TemplateID': '157', distributor,
'Accept': 'application/json, text/plain, */*', so_name: soName,
'Content-Type': 'application/json',
'sec-ch-ua-platform': '"Android"',
},
body: JSON.stringify({
date,
route,
distributor,
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');

View File

@ -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>

View File

@ -52,23 +52,25 @@ export function StoresPage() {
} }
return ( return (
<> <div className="flex flex-col h-full bg-transparent w-full relative">
<StoresView <div className="flex-1 overflow-y-auto pb-24">
refreshKey={refreshKey} <StoresView
onRowClick={(row) => { refreshKey={refreshKey}
const id = row.instance_id as number | string | undefined; onRowClick={(row) => {
if (id != null) navigate(`/stores/${id}`); const id = row.instance_id as number | string | undefined;
}} if (id != null) navigate(`/stores/${id}`);
onEditRow={(row) => { }}
setEditingInstanceId(row.instance_id as string | number); onEditRow={(row) => {
setEditTitle("Edit Store"); setEditingInstanceId(row.instance_id as string | number);
}} 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>
); );
} }

View File

@ -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 {