Compare commits
No commits in common. "dcbc288fafdd1d3b86190ebdbc9f822fe792e3ed" and "a8807155a26ef477bcf65d41a13ddf21cbd9b82b" have entirely different histories.
dcbc288faf
...
a8807155a2
@ -249,7 +249,3 @@ export const PIPELINE = {
|
|||||||
salesOfficers: '/api/papi2/sales-officers'
|
salesOfficers: '/api/papi2/sales-officers'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HIDDEN_FORM_FIELDS = [
|
|
||||||
'store_code_3'
|
|
||||||
];
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
setUserEmail(null);
|
setUserEmail(null);
|
||||||
setIsAdmin(false);
|
setIsAdmin(false);
|
||||||
setRoles([]);
|
setRoles([]);
|
||||||
localStorage.clear();
|
localStorage.removeItem('krishna_sales_user_email');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -130,7 +130,7 @@ export function CallDetail({
|
|||||||
// Extract store info with fallbacks matching reference
|
// Extract store info with fallbacks matching reference
|
||||||
const selectStore = (rowSrc.select_store || data?.select_store || {}) as Record<string, any>;
|
const selectStore = (rowSrc.select_store || data?.select_store || {}) as Record<string, any>;
|
||||||
const storeName = selectStore.business_name || selectStore.store_name || rowSrc.store_name || 'No data';
|
const storeName = selectStore.business_name || selectStore.store_name || rowSrc.store_name || 'No data';
|
||||||
const storeCode = selectStore.store_code_2 || selectStore.code || 'No data';
|
const storeCode = selectStore.store_code || selectStore.code || 'No data';
|
||||||
const ownerName = selectStore.owner_name || selectStore.contact_person || 'No data';
|
const ownerName = selectStore.owner_name || selectStore.contact_person || 'No data';
|
||||||
const phone = selectStore.phone_number?.phone || selectStore.phone_number?.phone_with_dial_code || selectStore.phone || 'No data';
|
const phone = selectStore.phone_number?.phone || selectStore.phone_number?.phone_with_dial_code || selectStore.phone || 'No data';
|
||||||
const email = selectStore.email || 'No data';
|
const email = selectStore.email || 'No data';
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export function StoreDetail({ instanceId, onBack, onEdit }: StoreDetailProps) {
|
|||||||
const badgeClass = isSuccess ? 'bg-emerald-100 text-emerald-700' : 'bg-blue-100 text-blue-700';
|
const badgeClass = isSuccess ? 'bg-emerald-100 text-emerald-700' : 'bg-blue-100 text-blue-700';
|
||||||
|
|
||||||
// Store Overview
|
// Store Overview
|
||||||
const storeCode = extract('store_code_2', remainingData) || '-';
|
const storeCode = extract('store_code', remainingData) || '-';
|
||||||
const businessName = extract('business_name', remainingData) || '-';
|
const businessName = extract('business_name', remainingData) || '-';
|
||||||
const area = extract('area', remainingData);
|
const area = extract('area', remainingData);
|
||||||
const completeAddress = extract('complete_address', remainingData);
|
const completeAddress = extract('complete_address', remainingData);
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
WfLookupField,
|
WfLookupField,
|
||||||
RadioField,
|
RadioField,
|
||||||
} from './fields';
|
} from './fields';
|
||||||
import { ORDER_BOOKING, STORE, DAILY_REPORTS, PIPELINE, HIDDEN_FORM_FIELDS } from '../../api/config';
|
import { ORDER_BOOKING, STORE, DAILY_REPORTS, PIPELINE } from '../../api/config';
|
||||||
import { isCategoryColumn, isProductColumn } from './fields/gridUtils';
|
import { isCategoryColumn, isProductColumn } from './fields/gridUtils';
|
||||||
|
|
||||||
export interface DynamicFormProps {
|
export interface DynamicFormProps {
|
||||||
@ -75,8 +75,14 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const imageFieldIds = new Set(
|
||||||
|
res.fields.filter(f => f.data_type === 'image' || f.data_type === 'file').map(f => f.id)
|
||||||
|
);
|
||||||
|
|
||||||
const mapPrefillData = (sourceData: Record<string, unknown>) => {
|
const mapPrefillData = (sourceData: Record<string, unknown>) => {
|
||||||
res.fields.forEach(f => {
|
res.fields.forEach(f => {
|
||||||
|
if (imageFieldIds.has(f.id)) return;
|
||||||
|
|
||||||
const getBaseId = (id: string) => id.replace(/_\d+$/, '');
|
const getBaseId = (id: string) => id.replace(/_\d+$/, '');
|
||||||
|
|
||||||
if (sourceData[f.id] !== undefined) {
|
if (sourceData[f.id] !== undefined) {
|
||||||
@ -126,13 +132,13 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
|
|
||||||
if (customPrefillData) {
|
if (customPrefillData) {
|
||||||
Object.entries(customPrefillData).forEach(([k, v]) => {
|
Object.entries(customPrefillData).forEach(([k, v]) => {
|
||||||
defaultValues[k] = v;
|
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chainedPrefillData) {
|
if (chainedPrefillData) {
|
||||||
Object.entries(chainedPrefillData).forEach(([k, v]) => {
|
Object.entries(chainedPrefillData).forEach(([k, v]) => {
|
||||||
defaultValues[k] = v;
|
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,13 +577,6 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
|
|
||||||
for (const f of fields) {
|
for (const f of fields) {
|
||||||
const val = finalValues[f.id];
|
const val = finalValues[f.id];
|
||||||
const isRemark = (f.name || '').toLowerCase().includes('remark') || (f.name || '').toLowerCase().includes('note');
|
|
||||||
|
|
||||||
if (isRemark && (val == null || val === '')) {
|
|
||||||
payload[f.id] = '';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val == null) continue;
|
if (val == null) continue;
|
||||||
|
|
||||||
if (f.data_type === 'phone' && typeof val === 'string') {
|
if (f.data_type === 'phone' && typeof val === 'string') {
|
||||||
@ -587,22 +586,16 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
phone: phoneNum,
|
phone: phoneNum,
|
||||||
phone_with_dial_code: `+91${phoneNum}`
|
phone_with_dial_code: `+91${phoneNum}`
|
||||||
};
|
};
|
||||||
} else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val)) {
|
} else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val) && val.length > 0 && val[0] instanceof File) {
|
||||||
const uploadedFiles = [];
|
const uploadedFiles = [];
|
||||||
for (const file of val) {
|
for (const file of val) {
|
||||||
if (file instanceof File) {
|
const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
||||||
const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
uploadedFiles.push(fileMeta);
|
||||||
uploadedFiles.push(fileMeta);
|
|
||||||
} else {
|
|
||||||
uploadedFiles.push(file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
payload[f.id] = uploadedFiles;
|
payload[f.id] = uploadedFiles;
|
||||||
} else if ((f.data_type === 'image' || f.data_type === 'file') && val instanceof File) {
|
} else if ((f.data_type === 'image' || f.data_type === 'file') && val instanceof File) {
|
||||||
const fileMeta = await client.uploadFile(val, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
const fileMeta = await client.uploadFile(val, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
||||||
payload[f.id] = [fileMeta];
|
payload[f.id] = [fileMeta];
|
||||||
} else if ((f.data_type === 'image' || f.data_type === 'file') && val && typeof val === 'object') {
|
|
||||||
payload[f.id] = [val];
|
|
||||||
} else if (f.data_type.startsWith('grid') || f.data_type === 'smart_grid') {
|
} else if (f.data_type.startsWith('grid') || f.data_type === 'smart_grid') {
|
||||||
const gridRows = Array.isArray(val) ? val : [];
|
const gridRows = Array.isArray(val) ? val : [];
|
||||||
const getFormattedGridVal = (colKey: string, rawVal: unknown, colDef?: FormScreenField) => {
|
const getFormattedGridVal = (colKey: string, rawVal: unknown, colDef?: FormScreenField) => {
|
||||||
@ -860,9 +853,6 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
f.id === 'total_kgs';
|
f.id === 'total_kgs';
|
||||||
if (isTotalField) return false;
|
if (isTotalField) return false;
|
||||||
}
|
}
|
||||||
if (HIDDEN_FORM_FIELDS.includes(f.id) || HIDDEN_FORM_FIELDS.includes(f.name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -343,11 +343,7 @@ export function LogVisitForm({ client, onSuccess, onCancel, onActivityChange }:
|
|||||||
const payload: Record<string, any> = {};
|
const payload: Record<string, any> = {};
|
||||||
validFieldIds.forEach(fieldId => {
|
validFieldIds.forEach(fieldId => {
|
||||||
const val = valuesRef.current[fieldId];
|
const val = valuesRef.current[fieldId];
|
||||||
const isRemark = fieldId.toLowerCase().includes('remark') || fieldId.toLowerCase().includes('note');
|
if (val !== undefined && val !== null && val !== '') {
|
||||||
|
|
||||||
if (isRemark && (val === undefined || val === null || val === '')) {
|
|
||||||
payload[fieldId] = '';
|
|
||||||
} else if (val !== undefined && val !== null && val !== '') {
|
|
||||||
payload[fieldId] = val;
|
payload[fieldId] = val;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { BASE_URL, APP_ID } from '../../../api/config';
|
|
||||||
|
|
||||||
export function ImagePreview({ file }: { file: File | any }) {
|
export function ImagePreview({ file }: { file: File }) {
|
||||||
const [url, setUrl] = useState<string | null>(null);
|
const [url, setUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (file instanceof File) {
|
const objectUrl = URL.createObjectURL(file);
|
||||||
const objectUrl = URL.createObjectURL(file);
|
setUrl(objectUrl);
|
||||||
setUrl(objectUrl);
|
return () => URL.revokeObjectURL(objectUrl);
|
||||||
return () => URL.revokeObjectURL(objectUrl);
|
|
||||||
} else if (file && file.uuid) {
|
|
||||||
setUrl(`${BASE_URL}/app/${APP_ID}/view/files/${file.uuid}/preview`);
|
|
||||||
}
|
|
||||||
}, [file]);
|
}, [file]);
|
||||||
|
|
||||||
if (!url) return null;
|
if (!url) return null;
|
||||||
@ -28,10 +23,10 @@ export function FileInput({
|
|||||||
label: string;
|
label: string;
|
||||||
type: 'image' | 'file' | string;
|
type: 'image' | 'file' | string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
value: File[] | any[] | undefined | null | unknown;
|
value: File[] | undefined | null | unknown;
|
||||||
onChange: (files: (File | any)[]) => void;
|
onChange: (files: File[]) => void;
|
||||||
}) {
|
}) {
|
||||||
const files = Array.isArray(value) ? value : (value ? [value] : []);
|
const files = Array.isArray(value) ? value : (value instanceof File ? [value] : []);
|
||||||
|
|
||||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.files) {
|
if (e.target.files) {
|
||||||
@ -73,7 +68,7 @@ export function FileInput({
|
|||||||
<ImagePreview file={file} />
|
<ImagePreview file={file} />
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center p-2 text-xs text-center break-all overflow-hidden text-muted">
|
<div className="w-full h-full flex items-center justify-center p-2 text-xs text-center break-all overflow-hidden text-muted">
|
||||||
{file.name || file.original_name || 'File'}
|
{file.name}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -203,9 +203,9 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ResponsiveContainer width="100%" height="100%" className="focus:outline-none [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none" style={{ outline: 'none' }}>
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
{chartType === 3 || chartType === 4 ? (
|
{chartType === 3 || chartType === 4 ? (
|
||||||
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }}
|
contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }}
|
||||||
itemStyle={{ color: '#0F172A', fontWeight: '500' }}
|
itemStyle={{ color: '#0F172A', fontWeight: '500' }}
|
||||||
@ -219,25 +219,22 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
|||||||
cy="50%"
|
cy="50%"
|
||||||
outerRadius={100}
|
outerRadius={100}
|
||||||
innerRadius={chartType === 4 ? 65 : 0}
|
innerRadius={chartType === 4 ? 65 : 0}
|
||||||
style={{ outline: 'none' }}
|
|
||||||
activeShape={false}
|
|
||||||
className="focus:outline-none outline-none"
|
|
||||||
>
|
>
|
||||||
{finalData.map((_, index) => (
|
{finalData.map((_, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} style={{ outline: 'none' }} className="focus:outline-none outline-none" />
|
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
) : chartType === 1 ? (
|
) : chartType === 1 ? (
|
||||||
<LineChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
<LineChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
||||||
{renderChartContent()}
|
{renderChartContent()}
|
||||||
</LineChart>
|
</LineChart>
|
||||||
) : chartType === 2 ? (
|
) : chartType === 2 ? (
|
||||||
<AreaChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
<AreaChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
||||||
{renderChartContent()}
|
{renderChartContent()}
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
) : (
|
) : (
|
||||||
<BarChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
<BarChart data={finalData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
||||||
{renderChartContent()}
|
{renderChartContent()}
|
||||||
</BarChart>
|
</BarChart>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -109,9 +109,3 @@ code {
|
|||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
background: var(--code-bg);
|
background: var(--code-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove black border on recharts when clicking */
|
|
||||||
.recharts-wrapper,
|
|
||||||
.recharts-wrapper * {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -23,11 +23,11 @@ export function DailyLogsPage() {
|
|||||||
const mapComponent = (
|
const mapComponent = (
|
||||||
<div className="!bg-[var(--tiles-card-bg)] rounded-lg shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full min-h-[320px]">
|
<div className="!bg-[var(--tiles-card-bg)] rounded-lg shadow-sm border border-gray-100 overflow-hidden flex flex-col h-full min-h-[320px]">
|
||||||
<div className="px-5 py-4 border-b border-[var(--z-block-border)] flex items-center gap-2">
|
<div className="px-5 py-4 border-b border-[var(--z-block-border)] flex items-center gap-2">
|
||||||
<MapPin size={18} className="text-slate-400" />
|
<MapPin size={18} className="text-slate-400" />
|
||||||
<h3 className="text-md font-semibold text-[var(--z-text-default)]">Activity Locations</h3>
|
<h3 className="text-md font-semibold text-[var(--z-text-default)]">Activity Locations</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 relative bg-slate-50">
|
<div className="flex-1 relative bg-slate-50">
|
||||||
<DailyLogMap />
|
<DailyLogMap />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -38,7 +38,7 @@ export function DailyLogsPage() {
|
|||||||
<DailyLogsView
|
<DailyLogsView
|
||||||
mapComponent={mapComponent}
|
mapComponent={mapComponent}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
presetAlias=""
|
presetAlias="my_logs"
|
||||||
onRowClick={(row) => {
|
onRowClick={(row) => {
|
||||||
const id = row.instance_id as number | string | undefined;
|
const id = row.instance_id as number | string | undefined;
|
||||||
if (id != null) navigate(`/daily/${id}`);
|
if (id != null) navigate(`/daily/${id}`);
|
||||||
@ -112,10 +112,10 @@ export function DailyLogsPage() {
|
|||||||
{instanceId != null && (
|
{instanceId != null && (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<DailyLogDetail
|
<DailyLogDetail
|
||||||
instanceId={instanceId}
|
instanceId={instanceId}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
onPunchOut={() => setPunchOutInstanceId(instanceId)}
|
onPunchOut={() => setPunchOutInstanceId(instanceId)}
|
||||||
onBack={() => navigate('/daily')}
|
onBack={() => navigate('/daily')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user