Added check spelling for country names

This commit is contained in:
GotthardG 2024-12-20 14:14:53 +01:00
parent 0108719a84
commit 9cb6ffbfb4
5 changed files with 349 additions and 11 deletions

View File

@ -1,11 +1,11 @@
{
"name": "mheidi-frontend-v2",
"name": "heidi-frontend-v2",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mheidi-frontend-v2",
"name": "heidi-frontend-v2",
"version": "0.0.0",
"dependencies": {
"@aldabil/react-scheduler": "^2.9.5",
@ -27,6 +27,7 @@
"dotenv": "^16.4.7",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"fuse.js": "^7.0.0",
"openapi-typescript-codegen": "^0.29.0",
"react": "^18.3.1",
"react-big-calendar": "^1.15.0",
@ -1160,9 +1161,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz",
"integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz",
"integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -4225,6 +4226,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fuse.js": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
"integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
"license": "Apache-2.0",
"engines": {
"node": ">=10"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -5071,9 +5081,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{

View File

@ -12,7 +12,7 @@
"start-test": "vite --mode test",
"start-prod": "vite --mode prod",
"watch:openapi": "node fetch-openapi.js"
},
},
"dependencies": {
"@aldabil/react-scheduler": "^2.9.5",
"@bitnoi.se/react-scheduler": "^0.3.1",
@ -33,6 +33,7 @@
"dotenv": "^16.4.7",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"fuse.js": "^7.0.0",
"openapi-typescript-codegen": "^0.29.0",
"react": "^18.3.1",
"react-big-calendar": "^1.15.0",

View File

@ -0,0 +1,198 @@
export const CountryList = [
'Afghanistan',
'Albania',
'Algeria',
'Andorra',
'Angola',
'Antigua and Barbuda',
'Argentina',
'Armenia',
'Australia',
'Austria',
'Azerbaijan',
'Bahamas',
'Bahrain',
'Bangladesh',
'Barbados',
'Belarus',
'Belgium',
'Belize',
'Benin',
'Bhutan',
'Bolivia',
'Bosnia and Herzegovina',
'Botswana',
'Brazil',
'Brunei',
'Bulgaria',
'Burkina Faso',
'Burundi',
'Cabo Verde',
'Cambodia',
'Cameroon',
'Canada',
'Central African Republic',
'Chad',
'Chile',
'China',
'Colombia',
'Comoros',
'Congo (Congo-Brazzaville)',
'Costa Rica',
'Croatia',
'Cuba',
'Cyprus',
'Czechia (Czech Republic)',
'Denmark',
'Djibouti',
'Dominica',
'Dominican Republic',
'Ecuador',
'Egypt',
'El Salvador',
'Equatorial Guinea',
'Eritrea',
'Estonia',
'Eswatini (fmr. "Swaziland")',
'Ethiopia',
'Fiji',
'Finland',
'France',
'Gabon',
'Gambia',
'Georgia',
'Germany',
'Ghana',
'Greece',
'Grenada',
'Guatemala',
'Guinea',
'Guinea-Bissau',
'Guyana',
'Haiti',
'Holy See',
'Honduras',
'Hungary',
'Iceland',
'India',
'Indonesia',
'Iran',
'Iraq',
'Ireland',
'Israel',
'Italy',
'Jamaica',
'Japan',
'Jordan',
'Kazakhstan',
'Kenya',
'Kiribati',
'Korea (North)',
'Korea (South)',
'Kosovo',
'Kuwait',
'Kyrgyzstan',
'Laos',
'Latvia',
'Lebanon',
'Lesotho',
'Liberia',
'Libya',
'Liechtenstein',
'Lithuania',
'Luxembourg',
'Madagascar',
'Malawi',
'Malaysia',
'Maldives',
'Mali',
'Malta',
'Marshall Islands',
'Mauritania',
'Mauritius',
'Mexico',
'Micronesia',
'Moldova',
'Monaco',
'Mongolia',
'Montenegro',
'Morocco',
'Mozambique',
'Myanmar (formerly Burma)',
'Namibia',
'Nauru',
'Nepal',
'Netherlands',
'New Zealand',
'Nicaragua',
'Niger',
'Nigeria',
'North Macedonia (formerly Macedonia)',
'Norway',
'Oman',
'Pakistan',
'Palau',
'Palestine State',
'Panama',
'Papua New Guinea',
'Paraguay',
'Peru',
'Philippines',
'Poland',
'Portugal',
'Qatar',
'Romania',
'Russia',
'Rwanda',
'Saint Kitts and Nevis',
'Saint Lucia',
'Saint Vincent and the Grenadines',
'Samoa',
'San Marino',
'Sao Tome and Principe',
'Saudi Arabia',
'Senegal',
'Serbia',
'Seychelles',
'Sierra Leone',
'Singapore',
'Slovakia',
'Slovenia',
'Solomon Islands',
'Somalia',
'South Africa',
'South Sudan',
'Spain',
'Sri Lanka',
'Sudan',
'Suriname',
'Sweden',
'Switzerland',
'Syria',
'Tajikistan',
'Tanzania',
'Thailand',
'Timor-Leste',
'Togo',
'Tonga',
'Trinidad and Tobago',
'Tunisia',
'Turkey',
'Turkmenistan',
'Tuvalu',
'Uganda',
'Ukraine',
'United Arab Emirates',
'United Kingdom',
'United States of America',
'Uruguay',
'Uzbekistan',
'Vanuatu',
'Venezuela',
'Vietnam',
'Yemen',
'Zambia',
'Zimbabwe',
];
export default CountryList

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import Fuse from 'fuse.js';
import {
Box, Button, TextField, Typography, Select, MenuItem, Stack, FormControl, InputLabel
} from '@mui/material';
@ -9,6 +10,7 @@ import {
OpenAPI, ShipmentCreate, ShipmentsService
} from '../../openapi';
import { useEffect } from 'react';
import { CountryList } from './CountryList'; // Import the list of countries
const MAX_COMMENTS_LENGTH = 200;
@ -18,7 +20,14 @@ interface ShipmentFormProps {
refreshShipments: () => void;
}
// Set up Fuse.js for fuzzy searching
const fuse = new Fuse(CountryList, {
threshold: 0.3,
includeScore: true,
});
const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshShipments }) => {
const [countrySuggestions, setCountrySuggestions] = React.useState<string[]>([]);
const [contactPersons, setContactPersons] = React.useState<ContactPerson[]>([]);
const [returnAddresses, setReturnAddresses] = React.useState<Address[]>([]);
const [proposals, setProposals] = React.useState<Proposal[]>([]);
@ -95,6 +104,19 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
getProposals();
}, []);
const handleCountryInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setNewReturnAddress({ ...newReturnAddress, country: value });
if (value) {
const suggestions = fuse.search(value).map((result) => result.item);
setCountrySuggestions(suggestions);
} else {
setCountrySuggestions([]);
}
};
const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email);
const validatePhoneNumber = (phone: string) => /^\+?[1-9]\d{1,14}$/.test(phone);
const validateZipCode = (zipcode: string) => {
@ -410,10 +432,37 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({ sx = {}, onCancel, refreshS
label="Country"
name="country"
value={newReturnAddress.country}
onChange={(e) => setNewReturnAddress({ ...newReturnAddress, country: e.target.value })}
onChange={handleCountryInputChange} // Ensure this matches the function name exactly
fullWidth
required
error={!newReturnAddress.country} // Optional: Add an error indicator if the input is empty
helperText={!newReturnAddress.country ? 'Country is required' : ''}
/>
{/* Render country suggestions below the input field */}
{countrySuggestions.length > 0 && (
<Box sx={{ marginTop: '0.5rem', border: '1px solid #ccc', borderRadius: '4px', padding: '0.5rem' }}>
{countrySuggestions.map((suggestion, index) => (
<Typography
key={index}
sx={{
cursor: 'pointer',
padding: '0.2rem 0',
'&:hover': {
backgroundColor: '#f0f0f0',
},
}}
onClick={() => {
// Update country field with selected suggestion
setNewReturnAddress({ ...newReturnAddress, country: suggestion });
setCountrySuggestions([]); // Clear suggestions
}}
>
{suggestion}
</Typography>
))}
</Box>
)}
<Button
variant="contained"
color="primary"

View File

@ -1,4 +1,6 @@
import React from 'react';
import Fuse from 'fuse.js';
import { CountryList } from '../components/CountryList';
import {
Container, Typography, List, ListItem, IconButton, TextField, Box, ListItemText, ListItemSecondaryAction, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button
} from '@mui/material';
@ -9,7 +11,13 @@ import AddIcon from '@mui/icons-material/Add';
import { AddressesService } from '../../openapi';
import type { Address, AddressCreate, AddressUpdate } from '../models/Address';
const fuse = new Fuse(CountryList, {
threshold: 0.3, // Lower threshold for stricter matches
includeScore: true,
});
const AddressManager: React.FC = () => {
const [countrySuggestions, setCountrySuggestions] = React.useState<string[]>([]);
const [addresses, setAddresses] = React.useState<Address[]>([]);
const [newAddress, setNewAddress] = React.useState<Partial<Address>>({
street: '',
@ -21,6 +29,20 @@ const AddressManager: React.FC = () => {
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
const [dialogOpen, setDialogOpen] = React.useState(false);
const [selectedAddress, setSelectedAddress] = React.useState<Address | null>(null);
const handleCountryInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
// Update the corresponding field in `newAddress`
setNewAddress({ ...newAddress, country: value });
// Perform fuzzy search using Fuse.js
if (value) {
const suggestions = fuse.search(value).map((result) => result.item);
setCountrySuggestions(suggestions); // Update suggestions state
} else {
setCountrySuggestions([]); // Clear suggestions if the input is empty
}
};
React.useEffect(() => {
const fetchAddresses = async () => {
@ -102,7 +124,65 @@ const AddressManager: React.FC = () => {
<TextField label="Street" name="street" value={newAddress.street || ''} onChange={handleInputChange} />
<TextField label="City" name="city" value={newAddress.city || ''} onChange={handleInputChange} />
<TextField label="Zipcode" name="zipcode" value={newAddress.zipcode || ''} onChange={handleInputChange} />
<TextField label="Country" name="country" value={newAddress.country || ''} onChange={handleInputChange} />
<TextField
label="Country"
name="country"
value={newAddress.country || ''}
onChange={handleCountryInputChange}
fullWidth
required
error={
!!((editAddressId !== null || newAddress.street || newAddress.city || newAddress.zipcode) && !newAddress.country)
} // Show an error only if in add/edit mode and country is empty
helperText={
!!((editAddressId !== null || newAddress.street || newAddress.city || newAddress.zipcode) && !newAddress.country)
? 'Country is required'
: ''
}
/>
{/* Render suggestions dynamically */}
<Box sx={{ position: 'relative' }}>
<TextField
label="Country"
name="country"
value={newAddress.country || ''}
onChange={handleCountryInputChange}
fullWidth
/>
{countrySuggestions.length > 0 && (
<Box
sx={{
border: '1px solid #ccc',
borderRadius: '4px',
background: 'white',
marginTop: '4px', /* Add space below the input */
position: 'absolute',
width: '100%', /* Match the TextField width */
zIndex: 10, /* Ensure it is above other UI elements */
}}
>
{countrySuggestions.map((suggestion, index) => (
<Typography
key={index}
sx={{
padding: '8px',
cursor: 'pointer',
'&:hover': { background: '#f5f5f5' },
}}
onClick={() => {
// Update country field with the clicked suggestion
setNewAddress({ ...newAddress, country: suggestion });
setCountrySuggestions([]); // Clear suggestions
}}
>
{suggestion}
</Typography>
))}
</Box>
)}
</Box>
<IconButton color="primary" onClick={handleAddOrUpdateAddress}>
{editAddressId !== null ? <SaveIcon /> : <AddIcon />}
</IconButton>