Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions global.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Accessible focus styles for keyboard navigation */
.web-input-accessible:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
55 changes: 30 additions & 25 deletions src/app/call/[id]/edit.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ const WebInput: React.FC<WebInputProps> = ({ label, placeholder, value, onChange
const isDark = colorScheme === 'dark';

const inputStyles = StyleSheet.flatten([
styles.webInput,
webStyles.webInput as any,
isDark ? styles.webInputDark : styles.webInputLight,
error ? styles.webInputError : {},
disabled ? styles.webInputDisabled : {},
disabled ? (webStyles.webInputDisabled as any) : {},
multiline ? { minHeight: rows * 24 + 16 } : {},
]);

Expand Down Expand Up @@ -152,7 +152,7 @@ const WebSelect: React.FC<WebSelectProps> = ({ label, placeholder, value, onChan
{required ? <Text style={styles.required}> *</Text> : null}
</Text>
<select
style={StyleSheet.flatten([styles.webSelect, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]) as React.CSSProperties}
style={StyleSheet.flatten([webStyles.webSelect as any, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]) as React.CSSProperties}
value={value}
onChange={(e) => onChange(e.target.value)}
>
Expand Down Expand Up @@ -315,6 +315,7 @@ export default function EditCallWeb() {

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showLocationPicker, showAddressSelection, showDispatchModal]);

const onSubmit = async (data: FormValues) => {
Expand Down Expand Up @@ -919,15 +920,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
} as const,
webInputDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand All @@ -941,23 +933,10 @@ const styles = StyleSheet.create({
webInputError: {
borderColor: '#ef4444',
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
rightElement: {
position: 'absolute',
right: 8,
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
cursor: 'pointer',
},
webSelectDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand Down Expand Up @@ -1211,3 +1190,29 @@ const styles = StyleSheet.create({
color: '#374151',
},
});

// Web-specific styles that use CSS-only properties
const webStyles: { [key: string]: React.CSSProperties } = {
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
cursor: 'pointer',
},
};
186 changes: 104 additions & 82 deletions src/app/call/new/index.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,21 @@
const isDark = colorScheme === 'dark';

const inputStyles = StyleSheet.flatten([
styles.webInput,
webStyles.webInput as any,
isDark ? styles.webInputDark : styles.webInputLight,
error ? styles.webInputError : {},
disabled ? styles.webInputDisabled : {},
disabled ? (webStyles.webInputDisabled as any) : {},
multiline ? { minHeight: rows * 24 + 16 } : {},
]);

// Add accessible focus styles for keyboard navigation
const accessibleInputStyles = {
...inputStyles,
outline: 'none',
} as React.CSSProperties & {
'&:focus-visible'?: React.CSSProperties;
};

return (
<View style={styles.webInputContainer}>
<Text style={StyleSheet.flatten([styles.webLabel, isDark ? styles.webLabelDark : styles.webLabelLight])}>
Expand All @@ -125,7 +133,8 @@
<View style={styles.inputWrapper}>
{multiline ? (
<textarea
style={inputStyles as React.CSSProperties}
className="web-input-accessible"
style={accessibleInputStyles as React.CSSProperties}
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
Expand All @@ -138,7 +147,8 @@
) : (
<input
type="text"
style={inputStyles as React.CSSProperties}
className="web-input-accessible"
style={accessibleInputStyles as React.CSSProperties}
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
Expand Down Expand Up @@ -171,17 +181,21 @@
const { colorScheme } = useColorScheme();
const isDark = colorScheme === 'dark';

const selectStyles = StyleSheet.flatten([webStyles.webSelect as any, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]);

// Add accessible focus styles for keyboard navigation
const accessibleSelectStyles = {
...selectStyles,
outline: 'none',
} as React.CSSProperties;

return (
<View style={styles.webInputContainer}>
<Text style={StyleSheet.flatten([styles.webLabel, isDark ? styles.webLabelDark : styles.webLabelLight])}>
{label}
{required ? <Text style={styles.required}> *</Text> : null}
</Text>
<select
style={StyleSheet.flatten([styles.webSelect, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]) as React.CSSProperties}
value={value}
onChange={(e) => onChange(e.target.value)}
>
<select className="web-input-accessible" style={accessibleSelectStyles} value={value} onChange={(e) => onChange(e.target.value)}>
<option value="">{placeholder}</option>
{options.map((option) => (
<option key={option.id} value={option.name}>
Expand Down Expand Up @@ -279,6 +293,61 @@
});
}, [trackEvent, callPriorities.length, callTypes.length]);

const onSubmit = useCallback(
async (data: FormValues) => {
try {
setIsSubmitting(true);

if (selectedLocation?.latitude && selectedLocation?.longitude) {
data.latitude = selectedLocation.latitude;
data.longitude = selectedLocation.longitude;
}

const priority = callPriorities.find((p) => p.Name === data.priority);
const type = callTypes.find((t) => t.Name === data.type);

if (!priority) {
setIsSubmitting(false);
toast.error(t('calls.invalid_priority'));
return;
}

if (!type) {
setIsSubmitting(false);
toast.error(t('calls.invalid_type'));
return;
}

await createCall({
name: data.name,
nature: data.nature,
priority: priority.Id,
type: type.Id,
note: data.note,
address: data.address,
latitude: data.latitude,
longitude: data.longitude,
what3words: data.what3words,
plusCode: data.plusCode,
dispatchUsers: data.dispatchSelection?.users,
dispatchGroups: data.dispatchSelection?.groups,
dispatchRoles: data.dispatchSelection?.roles,
dispatchUnits: data.dispatchSelection?.units,
dispatchEveryone: data.dispatchSelection?.everyone,
});

toast.success(t('calls.create_success'));
router.push('/calls' as Href);
} catch (err) {
console.error('Error creating call:', err);
toast.error(t('calls.create_error'));
} finally {
setIsSubmitting(false);
}
},
[selectedLocation, callPriorities, callTypes, toast, t, router]

Check warning on line 348 in src/app/call/new/index.web.tsx

View workflow job for this annotation

GitHub Actions / test

React Hook useCallback has an unnecessary dependency: 'router'. Either exclude it or remove the dependency array. Outer scope values like 'router' aren't valid dependencies because mutating them doesn't re-render the component
);

// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
Expand All @@ -303,57 +372,8 @@

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit, router]);

const onSubmit = async (data: FormValues) => {
try {
setIsSubmitting(true);

if (selectedLocation?.latitude && selectedLocation?.longitude) {
data.latitude = selectedLocation.latitude;
data.longitude = selectedLocation.longitude;
}

const priority = callPriorities.find((p) => p.Name === data.priority);
const type = callTypes.find((t) => t.Name === data.type);

if (!priority) {
toast.error(t('calls.invalid_priority'));
return;
}

if (!type) {
toast.error(t('calls.invalid_type'));
return;
}

await createCall({
name: data.name,
nature: data.nature,
priority: priority.Id,
type: type.Id,
note: data.note,
address: data.address,
latitude: data.latitude,
longitude: data.longitude,
what3words: data.what3words,
plusCode: data.plusCode,
dispatchUsers: data.dispatchSelection?.users,
dispatchGroups: data.dispatchSelection?.groups,
dispatchRoles: data.dispatchSelection?.roles,
dispatchUnits: data.dispatchSelection?.units,
dispatchEveryone: data.dispatchSelection?.everyone,
});

toast.success(t('calls.create_success'));
router.push('/calls' as Href);
} catch (err) {
console.error('Error creating call:', err);
toast.error(t('calls.create_error'));
} finally {
setIsSubmitting(false);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit]);

const handleLocationSelected = useCallback(
(location: { latitude: number; longitude: number; address?: string }) => {
Expand Down Expand Up @@ -1019,15 +1039,6 @@
flexDirection: 'row',
alignItems: 'center',
},
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
} as const,
webInputDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand All @@ -1041,23 +1052,10 @@
webInputError: {
borderColor: '#ef4444',
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
rightElement: {
position: 'absolute',
right: 8,
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
cursor: 'pointer',
},
webSelectDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand Down Expand Up @@ -1311,3 +1309,27 @@
color: '#374151',
},
});

// Web-specific styles that use CSS-only properties
const webStyles: { [key: string]: React.CSSProperties } = {
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
cursor: 'pointer',
},
};