classify web
This commit is contained in:
853
components/PagesComponent/AdsListing/ManualAddress.jsx
Normal file
853
components/PagesComponent/AdsListing/ManualAddress.jsx
Normal file
@@ -0,0 +1,853 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
getAreasApi,
|
||||
getCitiesApi,
|
||||
getCoutriesApi,
|
||||
getStatesApi,
|
||||
} from "@/utils/api";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { t } from "@/utils";
|
||||
|
||||
const ManualAddress = ({
|
||||
showManualAddress,
|
||||
setShowManualAddress,
|
||||
setLocation,
|
||||
}) => {
|
||||
const [CountryStore, setCountryStore] = useState({
|
||||
Countries: [],
|
||||
SelectedCountry: {},
|
||||
CountrySearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
countryOpen: false,
|
||||
isLoading: false,
|
||||
});
|
||||
const [StateStore, setStateStore] = useState({
|
||||
States: [],
|
||||
SelectedState: {},
|
||||
StateSearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
stateOpen: false,
|
||||
isLoading: false,
|
||||
});
|
||||
const [CityStore, setCityStore] = useState({
|
||||
Cities: [],
|
||||
SelectedCity: {},
|
||||
CitySearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
cityOpen: false,
|
||||
});
|
||||
const [AreaStore, setAreaStore] = useState({
|
||||
Areas: [],
|
||||
SelectedArea: {},
|
||||
AreaSearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
areaOpen: false,
|
||||
isLoading: false,
|
||||
});
|
||||
const [Address, setAddress] = useState("");
|
||||
const [fieldErrors, setFieldErrors] = useState({});
|
||||
const isCountrySelected =
|
||||
Object.keys(CountryStore?.SelectedCountry).length > 0;
|
||||
// Check if areas exist for the selected city
|
||||
const hasAreas = AreaStore?.Areas.length > 0;
|
||||
// Infinite scroll refs
|
||||
const { ref: stateRef, inView: stateInView } = useInView();
|
||||
const { ref: countryRef, inView: countryInView } = useInView();
|
||||
const { ref: cityRef, inView: cityInView } = useInView();
|
||||
const { ref: areaRef, inView: areaInView } = useInView();
|
||||
|
||||
const getCountriesData = async (search, page) => {
|
||||
try {
|
||||
setCountryStore((prev) => ({
|
||||
...prev,
|
||||
isLoading: true,
|
||||
Countries: search ? [] : prev.Countries, // Clear list if searching
|
||||
}));
|
||||
// Fetch countries
|
||||
const params = {};
|
||||
if (search) {
|
||||
params.search = search; // Send only 'search' if provided
|
||||
} else {
|
||||
params.page = page; // Send only 'page' if no search
|
||||
}
|
||||
|
||||
const res = await getCoutriesApi.getCoutries(params);
|
||||
let allCountries;
|
||||
if (page > 1) {
|
||||
allCountries = [...CountryStore?.Countries, ...res?.data?.data?.data];
|
||||
} else {
|
||||
allCountries = res?.data?.data?.data;
|
||||
}
|
||||
setCountryStore((prev) => ({
|
||||
...prev,
|
||||
currentPage: res?.data?.data?.current_page,
|
||||
Countries: allCountries,
|
||||
hasMore:
|
||||
res?.data?.data?.current_page < res?.data?.data?.last_page
|
||||
? true
|
||||
: false,
|
||||
isLoading: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error fetching countries data:", error);
|
||||
setCountryStore((prev) => ({ ...prev, isLoading: false }));
|
||||
}
|
||||
};
|
||||
const getStatesData = async (search, page) => {
|
||||
try {
|
||||
setStateStore((prev) => ({
|
||||
...prev,
|
||||
isLoading: true,
|
||||
States: search ? [] : prev.States,
|
||||
}));
|
||||
const params = {
|
||||
country_id: CountryStore?.SelectedCountry?.id,
|
||||
};
|
||||
if (search) {
|
||||
params.search = search; // Send only 'search' if provided
|
||||
} else {
|
||||
params.page = page; // Send only 'page' if no search
|
||||
}
|
||||
|
||||
const res = await getStatesApi.getStates(params);
|
||||
|
||||
let allStates;
|
||||
if (page > 1) {
|
||||
allStates = [...StateStore?.States, ...res?.data?.data?.data];
|
||||
} else {
|
||||
allStates = res?.data?.data?.data;
|
||||
}
|
||||
|
||||
setStateStore((prev) => ({
|
||||
...prev,
|
||||
currentPage: res?.data?.data?.current_page,
|
||||
States: allStates,
|
||||
hasMore:
|
||||
res?.data?.data?.current_page < res?.data?.data?.last_page
|
||||
? true
|
||||
: false,
|
||||
isLoading: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error fetching states data:", error);
|
||||
setStateStore((prev) => ({ ...prev, isLoading: false }));
|
||||
return [];
|
||||
}
|
||||
};
|
||||
const getCitiesData = async (search, page) => {
|
||||
try {
|
||||
setCityStore((prev) => ({
|
||||
...prev,
|
||||
isLoading: true,
|
||||
Cities: search ? [] : prev.Cities,
|
||||
}));
|
||||
const params = {
|
||||
state_id: StateStore?.SelectedState?.id,
|
||||
};
|
||||
if (search) {
|
||||
params.search = search; // Send only 'search' if provided
|
||||
} else {
|
||||
params.page = page; // Send only 'page' if no search
|
||||
}
|
||||
|
||||
const res = await getCitiesApi.getCities(params);
|
||||
let allCities;
|
||||
if (page > 1) {
|
||||
allCities = [...CityStore?.Cities, ...res?.data?.data?.data];
|
||||
} else {
|
||||
allCities = res?.data?.data?.data;
|
||||
}
|
||||
setCityStore((prev) => ({
|
||||
...prev,
|
||||
currentPage: res?.data?.data?.current_page,
|
||||
Cities: allCities,
|
||||
hasMore:
|
||||
res?.data?.data?.current_page < res?.data?.data?.last_page
|
||||
? true
|
||||
: false,
|
||||
isLoading: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error fetching cities data:", error);
|
||||
setCityStore((prev) => ({ ...prev, isLoading: false }));
|
||||
return [];
|
||||
}
|
||||
};
|
||||
const getAreaData = async (search, page) => {
|
||||
try {
|
||||
setAreaStore((prev) => ({
|
||||
...prev,
|
||||
isLoading: true,
|
||||
Areas: search ? [] : prev.Areas,
|
||||
}));
|
||||
const params = {
|
||||
city_id: CityStore?.SelectedCity?.id,
|
||||
};
|
||||
if (search) {
|
||||
params.search = search;
|
||||
} else {
|
||||
params.page = page;
|
||||
}
|
||||
const res = await getAreasApi.getAreas(params);
|
||||
let allArea;
|
||||
if (page > 1) {
|
||||
allArea = [...AreaStore?.Areas, ...res?.data?.data?.data];
|
||||
} else {
|
||||
allArea = res?.data?.data?.data;
|
||||
}
|
||||
setAreaStore((prev) => ({
|
||||
...prev,
|
||||
currentPage: res?.data?.data?.current_page,
|
||||
Areas: allArea,
|
||||
hasMore:
|
||||
res?.data?.data?.current_page < res?.data?.data?.last_page
|
||||
? true
|
||||
: false,
|
||||
isLoading: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error fetching areas data:", error);
|
||||
setAreaStore((prev) => ({ ...prev, isLoading: false }));
|
||||
return [];
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (showManualAddress) {
|
||||
getCountriesData(CountryStore?.CountrySearch, 1);
|
||||
}
|
||||
}, 500);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [CountryStore?.CountrySearch, showManualAddress]);
|
||||
useEffect(() => {
|
||||
if (CountryStore?.SelectedCountry?.id) {
|
||||
const timeout = setTimeout(() => {
|
||||
getStatesData(StateStore?.StateSearch, 1);
|
||||
}, 500);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
}, [CountryStore?.SelectedCountry?.id, StateStore?.StateSearch]);
|
||||
useEffect(() => {
|
||||
if (StateStore?.SelectedState?.id) {
|
||||
const timeout = setTimeout(() => {
|
||||
getCitiesData(CityStore?.CitySearch, 1);
|
||||
}, 500);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
}, [StateStore?.SelectedState?.id, CityStore?.CitySearch]);
|
||||
useEffect(() => {
|
||||
if (CityStore?.SelectedCity?.id) {
|
||||
const timeout = setTimeout(() => {
|
||||
getAreaData(AreaStore?.AreaSearch, 1);
|
||||
}, 500);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
}, [CityStore?.SelectedCity?.id, AreaStore?.AreaSearch]);
|
||||
|
||||
// Trigger infinite scroll when refs come into view
|
||||
useEffect(() => {
|
||||
if (CountryStore?.hasMore && !CountryStore?.isLoading && countryInView) {
|
||||
getCountriesData("", CountryStore?.currentPage + 1);
|
||||
}
|
||||
}, [
|
||||
countryInView,
|
||||
CountryStore?.hasMore,
|
||||
CountryStore?.isLoading,
|
||||
CountryStore?.currentPage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (StateStore?.hasMore && !StateStore?.isLoading && stateInView) {
|
||||
getStatesData("", StateStore?.currentPage + 1);
|
||||
}
|
||||
}, [
|
||||
stateInView,
|
||||
StateStore?.hasMore,
|
||||
StateStore?.isLoading,
|
||||
StateStore?.currentPage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (CityStore?.hasMore && !CityStore?.isLoading && cityInView) {
|
||||
getCitiesData("", CityStore?.currentPage + 1);
|
||||
}
|
||||
}, [
|
||||
cityInView,
|
||||
CityStore?.hasMore,
|
||||
CityStore?.isLoading,
|
||||
CityStore?.currentPage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (AreaStore?.hasMore && !AreaStore?.isLoading && areaInView) {
|
||||
getAreaData("", AreaStore?.currentPage + 1);
|
||||
}
|
||||
}, [
|
||||
areaInView,
|
||||
AreaStore?.hasMore,
|
||||
AreaStore?.isLoading,
|
||||
AreaStore?.currentPage,
|
||||
]);
|
||||
|
||||
const validateFields = () => {
|
||||
const errors = {};
|
||||
if (!CountryStore?.SelectedCountry?.name) errors.country = true;
|
||||
if (!StateStore?.SelectedState?.name) errors.state = true;
|
||||
if (!CityStore?.SelectedCity?.name) errors.city = true;
|
||||
if (!AreaStore?.SelectedArea?.name && !Address.trim())
|
||||
errors.address = true;
|
||||
return errors;
|
||||
};
|
||||
|
||||
const handleCountryChange = (value) => {
|
||||
const Country = CountryStore?.Countries.find(
|
||||
(country) => country.name === value
|
||||
);
|
||||
setCountryStore((prev) => ({
|
||||
...prev,
|
||||
SelectedCountry: Country,
|
||||
countryOpen: false,
|
||||
}));
|
||||
setStateStore({
|
||||
States: [],
|
||||
SelectedState: {},
|
||||
StateSearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
stateOpen: false,
|
||||
});
|
||||
setCityStore({
|
||||
Cities: [],
|
||||
SelectedCity: {},
|
||||
CitySearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
cityOpen: false,
|
||||
});
|
||||
setAreaStore({
|
||||
Areas: [],
|
||||
SelectedArea: {},
|
||||
AreaSearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
areaOpen: false,
|
||||
});
|
||||
setAddress("");
|
||||
};
|
||||
const handleStateChange = (value) => {
|
||||
const State = StateStore?.States.find((state) => state.name === value);
|
||||
setStateStore((prev) => ({
|
||||
...prev,
|
||||
SelectedState: State,
|
||||
stateOpen: false,
|
||||
}));
|
||||
setCityStore({
|
||||
Cities: [],
|
||||
SelectedCity: {},
|
||||
CitySearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
cityOpen: false,
|
||||
});
|
||||
setAreaStore({
|
||||
Areas: [],
|
||||
SelectedArea: {},
|
||||
AreaSearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
areaOpen: false,
|
||||
});
|
||||
setAddress("");
|
||||
};
|
||||
const handleCityChange = (value) => {
|
||||
const City = CityStore?.Cities.find((city) => city.name === value);
|
||||
setCityStore((prev) => ({
|
||||
...prev,
|
||||
SelectedCity: City,
|
||||
cityOpen: false,
|
||||
}));
|
||||
setAreaStore({
|
||||
Areas: [],
|
||||
SelectedArea: {},
|
||||
AreaSearch: "",
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
areaOpen: false,
|
||||
});
|
||||
setAddress("");
|
||||
};
|
||||
const handleAreaChange = (value) => {
|
||||
const chosenArea = AreaStore?.Areas.find((item) => item.name === value);
|
||||
setAreaStore((prev) => ({
|
||||
...prev,
|
||||
SelectedArea: chosenArea,
|
||||
areaOpen: false,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const errors = validateFields();
|
||||
setFieldErrors(errors);
|
||||
if (Object.keys(errors).length > 0) return;
|
||||
|
||||
// Build address parts array and filter out empty values
|
||||
|
||||
const addressParts = [];
|
||||
const addressPartsTranslated = [];
|
||||
|
||||
if (hasAreas && AreaStore?.SelectedArea?.name) {
|
||||
addressParts.push(AreaStore.SelectedArea.name);
|
||||
addressPartsTranslated.push(
|
||||
AreaStore.SelectedArea.translated_name || AreaStore.SelectedArea.name
|
||||
);
|
||||
} else if (Address.trim()) {
|
||||
addressParts.push(Address.trim());
|
||||
addressPartsTranslated.push(Address.trim());
|
||||
}
|
||||
if (CityStore?.SelectedCity?.name) {
|
||||
addressParts.push(CityStore.SelectedCity.name);
|
||||
addressPartsTranslated.push(
|
||||
CityStore.SelectedCity.translated_name || CityStore.SelectedCity.name
|
||||
);
|
||||
}
|
||||
if (StateStore?.SelectedState?.name) {
|
||||
addressParts.push(StateStore.SelectedState.name);
|
||||
addressPartsTranslated.push(
|
||||
StateStore.SelectedState.translated_name ||
|
||||
StateStore.SelectedState.name
|
||||
);
|
||||
}
|
||||
if (CountryStore?.SelectedCountry?.name) {
|
||||
addressParts.push(CountryStore.SelectedCountry.name);
|
||||
addressPartsTranslated.push(
|
||||
CountryStore.SelectedCountry.translated_name ||
|
||||
CountryStore.SelectedCountry.name
|
||||
);
|
||||
}
|
||||
|
||||
const formattedAddress = addressParts.join(", ");
|
||||
const formattedAddressTranslated = addressPartsTranslated.join(", ");
|
||||
|
||||
const locationData = {
|
||||
country: CountryStore?.SelectedCountry?.name || "",
|
||||
state: StateStore?.SelectedState?.name || "",
|
||||
city: CityStore?.SelectedCity?.name || "",
|
||||
formattedAddress: formattedAddress,
|
||||
address_translated: formattedAddressTranslated,
|
||||
lat: CityStore?.SelectedCity?.latitude || null,
|
||||
long: CityStore?.SelectedCity?.longitude || null,
|
||||
area_id: AreaStore?.SelectedArea?.id || null,
|
||||
};
|
||||
setLocation(locationData);
|
||||
setShowManualAddress(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={showManualAddress} onOpenChange={setShowManualAddress}>
|
||||
<DialogContent className="">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("manuAddAddress")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 py-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Popover
|
||||
modal
|
||||
open={CountryStore?.countryOpen}
|
||||
onOpenChange={(isOpen) =>
|
||||
setCountryStore((prev) => ({ ...prev, countryOpen: isOpen }))
|
||||
}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={CountryStore?.countryOpen}
|
||||
className={`w-full justify-between outline-none ${fieldErrors.country ? "border-red-500" : ""
|
||||
}`}
|
||||
>
|
||||
{CountryStore?.SelectedCountry?.translated_name ||
|
||||
CountryStore?.SelectedCountry?.name ||
|
||||
t("country")}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{fieldErrors.country && (
|
||||
<span className="text-red-500 text-sm">
|
||||
{t("countryRequired")}
|
||||
</span>
|
||||
)}
|
||||
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={t("searchCountries")}
|
||||
value={CountryStore.CountrySearch || ""}
|
||||
onValueChange={(val) => {
|
||||
setCountryStore((prev) => ({
|
||||
...prev,
|
||||
CountrySearch: val,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<CommandEmpty>
|
||||
{CountryStore.isLoading ? (
|
||||
<LoacationLoader />
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("noCountriesFound")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup className="max-h-[240px] overflow-y-auto">
|
||||
{CountryStore?.Countries?.map((country, index) => {
|
||||
const isLast =
|
||||
index === CountryStore?.Countries?.length - 1;
|
||||
return (
|
||||
<CommandItem
|
||||
key={country.id}
|
||||
value={country.name}
|
||||
onSelect={handleCountryChange}
|
||||
ref={isLast ? countryRef : null}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
CountryStore?.SelectedCountry?.name ===
|
||||
country?.name
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{country.translated_name || country.name}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
{CountryStore.isLoading &&
|
||||
CountryStore.Countries.length > 0 && <LoacationLoader />}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Popover
|
||||
modal
|
||||
open={StateStore?.stateOpen}
|
||||
onOpenChange={(isOpen) =>
|
||||
setStateStore((prev) => ({ ...prev, stateOpen: isOpen }))
|
||||
}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={StateStore?.stateOpen}
|
||||
className={`w-full justify-between outline-none ${fieldErrors.state ? "border-red-500" : ""
|
||||
}`}
|
||||
disabled={!isCountrySelected}
|
||||
>
|
||||
{StateStore?.SelectedState?.translated_name ||
|
||||
StateStore?.SelectedState?.name ||
|
||||
t("state")}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{fieldErrors.state && (
|
||||
<span className="text-red-500 text-sm">
|
||||
{t("stateRequired")}
|
||||
</span>
|
||||
)}
|
||||
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={t("searchStates")}
|
||||
value={StateStore.StateSearch || ""}
|
||||
onValueChange={(val) => {
|
||||
setStateStore((prev) => ({ ...prev, StateSearch: val }));
|
||||
}}
|
||||
/>
|
||||
<CommandEmpty>
|
||||
{StateStore.isLoading ? (
|
||||
<LoacationLoader />
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("noStatesFound")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup className="max-h-[240px] overflow-y-auto">
|
||||
{StateStore?.States?.map((state, index) => {
|
||||
const isLast = index === StateStore?.States?.length - 1;
|
||||
return (
|
||||
<CommandItem
|
||||
key={state.id}
|
||||
value={state.name}
|
||||
onSelect={handleStateChange}
|
||||
ref={isLast ? stateRef : null}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
StateStore?.SelectedState?.name === state?.name
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{state.translated_name || state.name}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
{StateStore.isLoading && StateStore.States.length > 0 && (
|
||||
<LoacationLoader />
|
||||
)}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<Popover
|
||||
modal
|
||||
open={CityStore?.cityOpen}
|
||||
onOpenChange={(isOpen) =>
|
||||
setCityStore((prev) => ({ ...prev, cityOpen: isOpen }))
|
||||
}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={CityStore?.cityOpen}
|
||||
className={`w-full justify-between outline-none ${fieldErrors.city ? "border-red-500" : ""
|
||||
}`}
|
||||
disabled={!StateStore?.SelectedState?.id}
|
||||
>
|
||||
{CityStore?.SelectedCity?.translated_name ||
|
||||
CityStore?.SelectedCity?.name ||
|
||||
t("city")}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{fieldErrors.city && (
|
||||
<span className="text-red-500 text-sm">
|
||||
{t("cityRequired")}
|
||||
</span>
|
||||
)}
|
||||
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={t("searchCities")}
|
||||
value={CityStore.CitySearch || ""}
|
||||
onValueChange={(val) => {
|
||||
setCityStore((prev) => ({ ...prev, CitySearch: val }));
|
||||
}}
|
||||
/>
|
||||
<CommandEmpty>
|
||||
{CityStore.isLoading ? (
|
||||
<LoacationLoader />
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("noCitiesFound")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup className="max-h-[240px] overflow-y-auto">
|
||||
{CityStore?.Cities?.map((city, index) => {
|
||||
const isLast = index === CityStore?.Cities?.length - 1;
|
||||
return (
|
||||
<CommandItem
|
||||
key={city.id}
|
||||
value={city.name}
|
||||
onSelect={handleCityChange}
|
||||
ref={isLast ? cityRef : null}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
CityStore?.SelectedCity?.name === city?.name
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{city.translated_name || city.name}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
{CityStore.isLoading && CityStore.Cities.length > 0 && (
|
||||
<LoacationLoader />
|
||||
)}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
{hasAreas || AreaStore?.AreaSearch ? (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Popover
|
||||
modal
|
||||
open={AreaStore?.areaOpen}
|
||||
onOpenChange={(isOpen) =>
|
||||
setAreaStore((prev) => ({ ...prev, areaOpen: isOpen }))
|
||||
}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={AreaStore?.areaOpen}
|
||||
className={`w-full justify-between outline-none ${fieldErrors.address ? "border-red-500" : ""
|
||||
}`}
|
||||
disabled={!CityStore?.SelectedCity?.id}
|
||||
>
|
||||
{AreaStore?.SelectedArea?.translated_name ||
|
||||
AreaStore?.SelectedArea?.name ||
|
||||
t("area")}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{fieldErrors.address && (
|
||||
<span className="text-red-500 text-sm">
|
||||
{t("areaRequired")}
|
||||
</span>
|
||||
)}
|
||||
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={t("searchAreas")}
|
||||
value={AreaStore.AreaSearch || ""}
|
||||
onValueChange={(val) => {
|
||||
setAreaStore((prev) => ({ ...prev, AreaSearch: val }));
|
||||
}}
|
||||
/>
|
||||
<CommandEmpty>
|
||||
{AreaStore.isLoading ? (
|
||||
<LoacationLoader />
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("noAreasFound")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup className="max-h-[240px] overflow-y-auto">
|
||||
{AreaStore?.Areas?.map((area, index) => {
|
||||
const isLast = index === AreaStore?.Areas?.length - 1;
|
||||
return (
|
||||
<CommandItem
|
||||
key={area.id}
|
||||
value={area.name}
|
||||
onSelect={handleAreaChange}
|
||||
ref={isLast ? areaRef : null}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
AreaStore?.SelectedArea?.name === area?.name
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{area.translated_name || area.name}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
{AreaStore.isLoading && AreaStore.Areas.length > 0 && (
|
||||
<LoacationLoader />
|
||||
)}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Textarea
|
||||
rows={5}
|
||||
className={`border p-2 outline-none rounded-md w-full ${fieldErrors.address ? "border-red-500" : ""
|
||||
}`}
|
||||
placeholder={t("enterAddre")}
|
||||
value={Address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
disabled={!CityStore?.SelectedCity?.id}
|
||||
/>
|
||||
{fieldErrors.address && (
|
||||
<span className="text-red-500 text-sm">
|
||||
{t("addressRequired")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter className="flex justify-end gap-2">
|
||||
<button onClick={() => setShowManualAddress(false)}>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
<button
|
||||
className="bg-primary p-2 px-4 rounded-md text-white font-medium"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManualAddress;
|
||||
|
||||
const LoacationLoader = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">
|
||||
{t("loading")}..
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user