Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
61ed8cc
doctype.data is now being accessed
Nischa1Mv Dec 9, 2025
833df0f
Merge branch 'main' of https://github.com/Nischa1Mv/offline-data-entr…
Nischa1Mv Dec 15, 2025
19c57c0
added the new doctype and adjusted the css beacuse of the long name
Nischa1Mv Jan 5, 2026
31faea1
- enhanced field visibility logic based on dynamic conditions in isFi…
Nischa1Mv Jan 5, 2026
4607b06
added CurrencyInput component and integrate it into FormDetail for cu…
Nischa1Mv Jan 5, 2026
4235eb0
added PhoneInput component and integrated it into FormDetail for phon…
Nischa1Mv Jan 5, 2026
67ea01a
added CheckboxInput component and integrated it into FormDetail for c…
Nischa1Mv Jan 6, 2026
feab457
added HeadingText component and integrated it into FormDetail for imp…
Nischa1Mv Jan 6, 2026
b575a71
added SectionBreak component and integrated it into FormDetail ,
Nischa1Mv Jan 6, 2026
029e693
only field.label will be passsed into label
Nischa1Mv Jan 6, 2026
267129e
refactor SectionBreak component to simplify label rendering logic
Nischa1Mv Jan 6, 2026
a13135b
the label for checkboxes is not needed , as it included in the compon…
Nischa1Mv Jan 6, 2026
82af9bd
refactor CheckboxInput and SectionBreak components for improved styli…
Nischa1Mv Jan 6, 2026
d78eae7
added a calender icon for data picker
Nischa1Mv Jan 6, 2026
a656e51
enhance input handling for numeric fields to restrict input types and…
Nischa1Mv Jan 6, 2026
64dc2d9
add validation functions for integer and float inputs, and format flo…
Nischa1Mv Jan 6, 2026
637f08f
add required field handling to form validation and display
Nischa1Mv Jan 6, 2026
4b971fc
refactor Forms component layout for improved styling and spacing
Nischa1Mv Jan 6, 2026
43e1bd0
refactor hash function for improved readability and structure
Nischa1Mv Jan 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,13 @@ export async function getAllDoctypesFromLocal(): Promise<
}
const parsed = parseJson<StoredDoctype>(value);
if (parsed?.payload) {
// Validate that payload has the expected structure
if (!parsed.payload.data) {
return;
}
if (!parsed.payload.fields) {
return;
}
result[normalizeDoctypeName(parsed.name)] = parsed.payload;
}
});
Expand Down Expand Up @@ -352,13 +359,50 @@ const defaultFetcher = async (name: string): Promise<DocType> => {
const response = await getDoctypeByName({
path: { form_name: name },
});

const data =
(response as any)?.data?.data ??
(response as any)?.data ??
(response as any);
if (!data) {
throw new Error(`Doctype response missing data for ${name}`);
}

// Validate that we have fields array
if (!data.fields || !Array.isArray(data.fields)) {
throw new Error(`Invalid doctype structure for ${name}: missing fields array`);
}

// Check if data object exists, if not try to create it from top-level properties
if (!data.data || typeof data.data !== 'object') {
// Look for doctype metadata at the top level
const doctypeMetadata: any = {};
const metadataFields = [
'name', 'creation', 'modified', 'modified_by', 'owner', 'docStatus',
'idx', 'issingle', 'istable', 'editable_grid', 'track_changes', 'module',
'autoname', 'name_case', 'sort_field', 'sort_order', 'readonly', 'in_create',
'allow_copy', 'allow_rename', 'allow_import', 'hide_toolbar', 'track_seen',
'max_attachments', 'document_type', 'engine', 'is_submittable',
'show_name_in_global_search', 'custom', 'beta', 'has_web_view',
'allow_guest_to_view', 'qick_entry', 'is_tree', 'track_views',
'all_events_in_timeline', 'allow_auto_repeat', 'show_preview_popup',
'email_append_to', 'index_web_pages_for_search', 'docType'
];

metadataFields.forEach(field => {
if (data[field] !== undefined) {
doctypeMetadata[field] = data[field];
}
});

// If we found metadata, create the nested structure
if (Object.keys(doctypeMetadata).length > 0) {
data.data = doctypeMetadata;
} else {
throw new Error(`Invalid doctype structure for ${name}: missing data object`);
}
}

return data as DocType;
};

Expand Down Expand Up @@ -594,6 +638,7 @@ export function extractFields(docType: DocType): RawField[] {
print_hide: field.print_hide,
report_hide: field.report_hide,
depends_on: field.depends_on,
reqd: field.reqd,
}));
}

Expand Down
18 changes: 10 additions & 8 deletions src/app/components/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import DateTimePicker, {
DateTimePickerAndroid,
type DateTimePickerEvent,
} from '@react-native-community/datetimepicker';
import { Calendar } from 'lucide-react-native';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
View,
Text,
TouchableOpacity,
Modal,
Platform,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import DateTimePicker, {
DateTimePickerAndroid,
type DateTimePickerEvent,
} from '@react-native-community/datetimepicker';
import { useTheme } from '../../context/ThemeContext';
import { useTranslation } from 'react-i18next';

interface DatePickerProps {
value?: string;
Expand Down Expand Up @@ -129,6 +130,7 @@ const DatePicker: React.FC<DatePickerProps> = ({
>
{value ? formatDisplayDate(value) : placeholder}
</Text>
<Calendar color={theme.subtext} size={20} />
</TouchableOpacity>

{Platform.OS === 'ios' && datePickerVisible && (
Expand Down
43 changes: 5 additions & 38 deletions src/app/components/SelectDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChevronDown } from 'lucide-react-native';
import React, { useEffect } from 'react';
import React from 'react';
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { useTheme } from '../../context/ThemeContext';

Expand All @@ -11,7 +11,6 @@ interface SelectDropdownProps {
isOpen: boolean;
onToggle: () => void;
containerZIndex?: number;
dependsOn?: string;
formData?: Record<string, any>;
}

Expand All @@ -23,8 +22,6 @@ const SelectDropdown: React.FC<SelectDropdownProps> = ({
isOpen,
onToggle,
containerZIndex,
dependsOn,
formData,
}) => {
const { theme } = useTheme();

Expand Down Expand Up @@ -55,49 +52,21 @@ const SelectDropdown: React.FC<SelectDropdownProps> = ({
maxHeight: 250,
};

// 🔥 Compute disabled state
const isDisabled = React.useMemo(() => {
if (!dependsOn) return false;

if (dependsOn.startsWith('eval:doc.')) {
const regex = /^eval:doc\.([a-zA-Z0-9_]+)\s*==\s*["'](.+)["']$/;
const match = dependsOn.match(regex);

if (match && formData) {
const [_, fieldName, expectedValue] = match;
return formData[fieldName] !== expectedValue;
}
return true;
}

return false;
}, [dependsOn, formData]);

// 🔄 Reset value when it becomes disabled
useEffect(() => {
if (isDisabled && value !== "") {
onValueChange("");
}
}, [isDisabled, value]);

return (
<View style={containerStyle}>
{/* Dropdown Toggle Button */}
<TouchableOpacity
className="h-[40px] w-full flex-row items-center justify-between rounded-md border px-3"
style={{
borderColor: theme.border,
backgroundColor: isDisabled ? '#373737ff' : theme.background,
opacity: isDisabled ? 0.5 : 1,
}}
onPress={() => {
if (!isDisabled) onToggle(); // prevent opening when disabled
backgroundColor: theme.background,
}}
onPress={onToggle}
>
<Text
className="flex-1"
style={{
color: isDisabled ? theme.subtext : (value ? theme.text : theme.subtext),
color: value ? theme.text : theme.subtext,
}}
>
{value || placeholder}
Expand Down Expand Up @@ -132,9 +101,7 @@ const SelectDropdown: React.FC<SelectDropdownProps> = ({
borderBottomColor:
optIndex < options.length - 1 ? theme.border : undefined,
}}
onPress={() => {
if (!isDisabled) onValueChange(trimmedOption);
}}
onPress={() => onValueChange(trimmedOption)}
>
<Text
style={{
Expand Down
51 changes: 51 additions & 0 deletions src/app/components/fields/CheckboxInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Check } from 'lucide-react-native';
import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { useTheme } from '../../../context/ThemeContext';

interface CheckboxInputProps {
value?: number | boolean;
onValueChange?: (value: number) => void;
label?: string;
}

const CheckboxInput: React.FC<CheckboxInputProps> = ({
value,
onValueChange,
label,
}) => {
const { theme } = useTheme();
const isChecked = value === 1 || value === true;

const handleToggle = () => {
if (onValueChange) {
onValueChange(isChecked ? 0 : 1);
}
};

return (
<TouchableOpacity
className="mt-2 flex-row items-center gap-3"
onPress={handleToggle}
>
<View
className="h-6 w-6 items-center justify-center rounded-full border-2"
style={{
borderColor: theme.border,
backgroundColor: isChecked
? theme.buttonBackground
: theme.background,
}}
>
{isChecked && <Check size={16} color={theme.buttonText} />}
</View>
{label && (
<Text className="font-sans text-sm font-medium leading-5 tracking-normal" style={{ color: theme.text }}>
{label}
</Text>
)}
</TouchableOpacity>
);
};

export default CheckboxInput;
73 changes: 73 additions & 0 deletions src/app/components/fields/CurrencyInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useState } from 'react';
import { TextInput as RNTextInput, Text, TextInputProps, View } from 'react-native';
import { useTheme } from '../../../context/ThemeContext';

interface CurrencyInputProps extends Omit<TextInputProps, 'style' | 'value' | 'onChangeText'> {
className?: string;
value?: string;
onChangeText?: (text: string) => void;
}

const formatCurrency = (value: string): string => {
// Remove all non-digit characters
const numericValue = value.replace(/[^0-9]/g, '');

if (!numericValue) return '';

// Add commas for Indian number system (lakhs and crores)
const number = parseInt(numericValue, 10);
return number.toLocaleString('en-IN');
};

const CurrencyInput: React.FC<CurrencyInputProps> = ({
className = "h-[40px] w-full rotate-0 rounded-md border pb-2.5 pl-3 pr-3 pt-2.5 opacity-100",
value,
onChangeText,
...props
}) => {
const { theme } = useTheme();
const [displayValue, setDisplayValue] = useState(formatCurrency(value || ''));

const handleTextChange = (text: string) => {
// Remove commas to get raw numeric value
const rawValue = text.replace(/,/g, '');

// Format for display
const formatted = formatCurrency(rawValue);
setDisplayValue(formatted);

// Pass raw numeric value to parent
if (onChangeText) {
onChangeText(rawValue);
}
};

// Update display value when prop value changes
React.useEffect(() => {
setDisplayValue(formatCurrency(value || ''));
}, [value]);

return (
<View className="relative">
<View className="absolute left-3 top-0 z-10 h-full justify-center">
<Text style={{ color: theme.text, fontSize: 16 }}>₹</Text>
</View>
<RNTextInput
className={className}
style={{
borderColor: theme.border,
backgroundColor: theme.background,
color: theme.text,
paddingLeft: 28, // Extra padding for rupee symbol
}}
placeholderTextColor={theme.subtext}
keyboardType="numeric"
value={displayValue}
onChangeText={handleTextChange}
{...props}
/>
</View>
);
};

export default CurrencyInput;
22 changes: 22 additions & 0 deletions src/app/components/fields/HeadingText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Text } from 'react-native';
import { useTheme } from '../../../context/ThemeContext';

interface HeadingTextProps {
label: string;
}

const HeadingText: React.FC<HeadingTextProps> = ({ label }) => {
const { theme } = useTheme();

return (
<Text
className="text-lg font-light leading-6"
style={{ color: theme.text }}
>
{label}
</Text>
);
};

export default HeadingText;
Loading