Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b58f7fc
#645 create feature dropdown component
PavelOlkhovoi Apr 28, 2026
5d995e7
#645 create new feature modal creation
PavelOlkhovoi Apr 28, 2026
f2d660c
#645 change icon for leitungen
PavelOlkhovoi Apr 28, 2026
961d77c
#645 add basic handler save feature
PavelOlkhovoi Apr 28, 2026
babc257
#645 add hardcoded data for lights creation
PavelOlkhovoi Apr 28, 2026
8a307d1
#645 add demo handler that creates mast and then lights
PavelOlkhovoi Apr 28, 2026
b9ad32f
#645 fix all empty placeholder
PavelOlkhovoi Apr 28, 2026
cb0d43e
#645 fix mast saving
PavelOlkhovoi Apr 28, 2026
4ec0d9f
#645 fly to saved mast
PavelOlkhovoi Apr 28, 2026
6deb94f
#645 add upload tab to to modal creation
PavelOlkhovoi Apr 28, 2026
061f956
#645 add new streets component
PavelOlkhovoi Apr 29, 2026
f99d129
#645 add data to streets combobox
PavelOlkhovoi Apr 29, 2026
8f46cbf
#645 add drafts planed for creations features
PavelOlkhovoi Apr 29, 2026
f14edf0
#645 fix showing created drafts
PavelOlkhovoi Apr 29, 2026
f1fc234
#645 hide server save handler
PavelOlkhovoi Apr 29, 2026
f44b797
#645 fix persisting features previously created
PavelOlkhovoi Apr 29, 2026
7500f96
#645 fix loosing focus in all texts fields
PavelOlkhovoi Apr 29, 2026
7c860d6
#645 fix saving data to uploading
PavelOlkhovoi Apr 29, 2026
09872af
#645 add geo selector to form creation
PavelOlkhovoi Apr 29, 2026
15b8d23
#645 not show ids in new planned for creation feature
PavelOlkhovoi Apr 29, 2026
5c22a56
#645 improve styles of create feature dropdown
PavelOlkhovoi Apr 29, 2026
bf319fc
#645 partially fix the first selection of first combo box
PavelOlkhovoi Apr 29, 2026
df294ac
#645 fix showing actions button in edit mode
PavelOlkhovoi Apr 29, 2026
1a5332b
#645 fix combobox with timeout 0
PavelOlkhovoi Apr 29, 2026
802ba9e
#645 add single save button to draft
PavelOlkhovoi Apr 29, 2026
d3d2db5
#645 rename close button
PavelOlkhovoi Apr 30, 2026
715e209
#645 fix features form synchronization
PavelOlkhovoi Apr 30, 2026
02007ca
#645 change selection after cancelling
PavelOlkhovoi Apr 30, 2026
2fee523
#645 add one tab for abzweigdose
PavelOlkhovoi Apr 30, 2026
65b65d9
#645 add new draft directly in datablatt
PavelOlkhovoi Apr 30, 2026
94e428c
#645 rename many entity in new draft
PavelOlkhovoi Apr 30, 2026
1ad168e
#645 fix resetting draft by geom selection
PavelOlkhovoi Apr 30, 2026
b5cd850
#655 fix position of geo select
PavelOlkhovoi Apr 30, 2026
4df53be
#645 fix streets selected behavior
PavelOlkhovoi Apr 30, 2026
6b17267
#645 fix titles for all new drafts
PavelOlkhovoi Apr 30, 2026
39a9400
#645 add draft styles for new selectors
PavelOlkhovoi Apr 30, 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
94 changes: 90 additions & 4 deletions apps/belis/desktop/src/components/commons/BelisMapWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from "../../config/mapLayerConfigs";
import type { LibreLayer } from "@carma-mapping/engines/maplibre";
import { AppDispatch, type RootState } from "../../store";
import { useMapPage } from "../../contexts/MapPageContext";
import BelisSidebar from "../ui/BelisSidebar";
import ArbeitsauftraegeSidebar from "../ui/ArbeitsauftraegeSidebar";
import { AA_SORT_BY_NUMMER_ASC, AA_SORT_BY_PROTOKOLLE_DESC } from "../../helper/aaSortHelpers";
Expand Down Expand Up @@ -121,6 +122,7 @@ import type { SidebarFeature } from "../ui/BelisSidebar";
import {
getAllDraftFeatures,
getDraftFeaturesCount,
getDraftFetchedData,
getGlobalEditMode,
} from "../../store/slices/featuresForms";
import {
Expand All @@ -129,6 +131,10 @@ import {
getAPDeletions,
} from "../../store/slices/arbeitsauftraegeDrafts";
import { prepareDraftFeatures } from "../../helper/prepareDraftFeatures";
import {
buildSyntheticFeature,
featureTypeToSourceLayer,
} from "../../helper/buildSyntheticFeature";
// import { useAaLassoSelection } from "../../hooks/useAaLassoSelection";

function buildAAFeatureCollection(
Expand Down Expand Up @@ -181,6 +187,7 @@ const BelisMapLibWrapper = ({
}: BelisMapLibWrapperProps) => {
const dispatch: AppDispatch = useDispatch();
const store = useStore<RootState>();
const { setOnSelectNextDraft, setOnOpenCreationDraft } = useMapPage();
const jwt = useSelector(getJWT);
const featureDataVersion = useSelector(getFeatureDataVersion);
const enabledLeitungstypen = useSelector(getEnabledLeitungstypen);
Expand Down Expand Up @@ -515,6 +522,15 @@ const BelisMapLibWrapper = ({
const allDraftFeatures = useSelector(getAllDraftFeatures);
const draftFeaturesCount = useSelector(getDraftFeaturesCount);

// Live fetchedData for creation drafts — avoids stale snapshot in fetchedFeatureData
const creationDraftKey =
rawFeature?.properties?._isCreation === true
? String(rawFeature.properties.id)
: undefined;
const liveDraftFetchedData = useSelector((state: RootState) =>
getDraftFetchedData(state, creationDraftKey)
);

// Draft features are stored as raw MapGeoJSON features (same structure as
// sidebar features from the map). No reconstruction needed — just extract
// and pass through prepareDraftFeatures for fk_standort normalization.
Expand Down Expand Up @@ -710,6 +726,19 @@ const BelisMapLibWrapper = ({
// fallback: !selectedFeature,
// });

// Creation drafts have synthetic fetchedData — skip API fetch
const featureIdStr = String(featureId ?? "");
if (featureIdStr.startsWith("create:")) {
const creationDraft =
store.getState().featuresForms?.drafts[featureIdStr];
if (creationDraft?.fetchedData) {
setFetchedFeatureData(creationDraft.fetchedData);
} else {
setFetchedFeatureData(null);
}
return;
}

const apiFeatureType = SOURCE_LAYER_TO_FEATURE_TYPE[sourceLayer ?? ""];
if (!apiFeatureType || !featureId) {
// Not a known BeLIS layer (e.g. ALKIS background); clear stale data
Expand Down Expand Up @@ -780,6 +809,11 @@ const BelisMapLibWrapper = ({
return;
}

if (rawFeature?.properties?._isCreation === true) {
setFeatureOnMap(true);
return;
}

const geometry = rawFeature?.geometry;
if (!geometry) {
console.log("[AA-DEBUG] no geometry, skipping");
Expand Down Expand Up @@ -2296,6 +2330,16 @@ const BelisMapLibWrapper = ({
feature: SidebarFeature
) => {
if (sidebarMode === "drafts") {
// Creation drafts have no MVT tile feature — select directly
if (feature.properties?._isCreation) {
dispatch(
setSelectedFeature({ ...feature, selected: true })
);
selectFeature(identifier, feature as any);
setFeatureOnMap(true);
return;
}

const sl = identifier.sourceLayer ?? "";
const dbPK = String(feature.properties?.id ?? identifier.id);

Expand Down Expand Up @@ -2333,29 +2377,70 @@ const BelisMapLibWrapper = ({
);

// After a draft is cancelled/removed, select the next remaining draft.
// If no drafts remain, clear the selection and close the datasheet.
const handleSelectNextDraft = useCallback(
(removedFeatureId: string) => {
if (sidebarMode !== "drafts") return;
if (sidebarMode !== "drafts") {
clearMapSelection();
closeDatasheet();
return;
}
// allDraftFeatures still contains the removed draft at this point
// because the selector reads from the store snapshot before the next render.
// Filter it out to get the remaining drafts.
const remaining = allDraftFeatures.filter(({ feature }) => {
if (!feature) return false;
if (feature.properties?._isCreation) {
return String(feature.properties.id) !== removedFeatureId;
}
const sl = feature.sourceLayer ?? "";
const pk = String(feature.properties?.id ?? "");
return `${sl}:${pk}` !== removedFeatureId;
});
if (remaining.length === 0) return;
if (remaining.length === 0) {
clearMapSelection();
closeDatasheet();
return;
}
const next = remaining[0];
const f = next.feature;
handleSidebarFeatureSelect(
{ source: f.source ?? "", sourceLayer: f.sourceLayer ?? "", id: f.id },
f
);
},
[sidebarMode, allDraftFeatures, handleSidebarFeatureSelect]
[sidebarMode, allDraftFeatures, handleSidebarFeatureSelect, clearMapSelection, closeDatasheet]
);

useEffect(() => {
setOnSelectNextDraft(() => handleSelectNextDraft);
return () => setOnSelectNextDraft(undefined);
}, [handleSelectNextDraft, setOnSelectNextDraft]);

const handleOpenCreationDraft = useCallback(
(featureType: string, draftKey: string) => {
const draft = store.getState().featuresForms?.drafts[draftKey];
const syntheticFeature =
draft?.feature ??
buildSyntheticFeature(featureType, draftKey, {}, draft?.geometry);
const sourceLayer =
featureTypeToSourceLayer[featureType] ?? featureType;
dispatch(setSelectedFeature({ ...syntheticFeature, selected: true }));
selectFeature(
{ source: "", sourceLayer, id: draftKey },
syntheticFeature as any
);
setFeatureOnMap(true);
openDatasheet();
},
[store, dispatch, selectFeature, openDatasheet]
);

useEffect(() => {
setOnOpenCreationDraft(() => handleOpenCreationDraft);
return () => setOnOpenCreationDraft(undefined);
}, [handleOpenCreationDraft, setOnOpenCreationDraft]);

return (
<div
className="relative flex"
Expand Down Expand Up @@ -2617,12 +2702,13 @@ const BelisMapLibWrapper = ({
<BelisDatasheetView
feature={selectedFeature}
rawFeature={rawFeature}
fetchedData={fetchedFeatureData}
fetchedData={liveDraftFetchedData ?? fetchedFeatureData}
featureType={
selectedFeature?.carmaInfo?.sourceLayer ||
selectedFeatureId?.sourceLayer ||
lastFeatureType
}
readOnly={!globalEditMode}
featureOnMap={featureOnMap}
onSelectNextDraft={handleSelectNextDraft}
/>
Expand Down
2 changes: 2 additions & 0 deletions apps/belis/desktop/src/components/commons/TopNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { incrementFeatureDataVersion } from "../../store/slices/featureCollection";
import { buildAddFeaturesToAAPayload } from "../../helper/buildNewAAFromFeatures";
import { updateDataByClassName } from "../../helper/apiMethods";
import CreateFeatureDropdown from "../ui/CreateFeatureDropdown";

const TopNavbar = () => {
const dispatch = useDispatch();
Expand Down Expand Up @@ -125,6 +126,7 @@ const TopNavbar = () => {
>
Fachobjekte
</NavLink>
<CreateFeatureDropdown />
<NavLink
to="/arbeitsauftraege"
className={({ isActive }) => `text-base hover:text-gray-600`}
Expand Down
35 changes: 27 additions & 8 deletions apps/belis/desktop/src/components/ui/BelisSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ export type { SidebarFeature };
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
import toTitleCase from "../../helper/toTitleCase";
import { isCreationDraftKey } from "../../store/slices/featuresForms";
import AuswahlBlock from "./AuswahlBlock";

const displayId = (id: unknown): string => {
if (id == null) return "?";
const s = String(id);
return isCreationDraftKey(s) ? "Entwurf" : s;
};

export const SELECTED_ROW_STYLE =
"bg-blue-50 hover:bg-blue-50 border-l-2 border-l-blue-500";

Expand Down Expand Up @@ -65,7 +72,7 @@ const defaultListItemExtractors: Record<
const p = feature.properties || {};
const title = p.schaltstellen_nummer
? `S ${p.schaltstellen_nummer}`
: `ID: ${p.id}`;
: `ID: ${displayId(p.id)}`;
return {
main: title,
upperright: toTitleCase(p.strasse || "") || "-",
Expand All @@ -76,7 +83,7 @@ const defaultListItemExtractors: Record<
const p = feature.properties || {};
const title = p.schaltstellen_nummer
? `S ${p.schaltstellen_nummer}`
: `ID: ${p.id}`;
: `ID: ${displayId(p.id)}`;
return {
main: title,
upperright: toTitleCase(p.strasse || "") || "-",
Expand All @@ -88,7 +95,7 @@ const defaultListItemExtractors: Record<
const laenge = p.laenge || p.length || "";
const laengeStr = laenge ? `${laenge}m` : "";
return {
main: `L-${p.id || "?"}`,
main: `L-${displayId(p.id)}`,
upperright: laengeStr,
subtitle: p.bezeichnung || p.leitungstyp || "",
};
Expand All @@ -99,31 +106,31 @@ const defaultListItemExtractors: Record<
? `, ${p.fk_querschnitt.groesse}mm`
: "";
return {
main: `L-${p.id}`,
main: `L-${displayId(p.id)}`,
upperright: p.fk_leitungstyp?.bezeichnung || "Leitung",
subtitle: aPart ? `Querschnitt${aPart}` : "",
};
},
mauerlaschen: (feature) => {
const p = feature.properties || {};
return {
main: `M-${p.laufende_nummer || p.id || "?"}`,
main: `M-${p.laufende_nummer || displayId(p.id)}`,
upperright: toTitleCase(p.strasse || "") || "-",
subtitle: p.bezeichnung || p.material || "Mauerlasche",
};
},
mauerlasche: (feature) => {
const p = feature.properties || {};
return {
main: `M-${p.laufende_nummer || p.id}`,
main: `M-${p.laufende_nummer || displayId(p.id)}`,
upperright: toTitleCase(p.fk_strassenschluessel?.strasse || "") || "-",
subtitle: p.fk_material?.bezeichnung || "Mauerlasche",
};
},
abzweigdose: (feature) => {
const p = feature.properties || {};
return {
main: `AZD-${p.id}`,
main: `AZD-${displayId(p.id)}`,
upperright: "",
subtitle: "Abzweigdose",
};
Expand All @@ -138,7 +145,7 @@ const genericExtractor = (feature: SidebarFeature): ListItemData => {
props.title ||
props.label ||
props.bezeichnung ||
`ID: ${feature.id || "?"}`;
`ID: ${displayId(feature.id)}`;

const upperright = toTitleCase(
props.strasse || props.street || props.typ || props.type || ""
Expand Down Expand Up @@ -544,6 +551,13 @@ const BelisSidebar = ({
});

const getListItem = (feature: SidebarFeature): ListItemData => {
if (feature.properties?._isCreation) {
return {
main: feature.properties._creationLabel || "Neu",
upperright: "Neu",
subtitle: "Entwurf",
};
}
const layerKey = feature.sourceLayer || feature.source || "";
const extractor =
listItemExtractors?.[layerKey] ||
Expand Down Expand Up @@ -695,6 +709,11 @@ const BelisSidebar = ({
<div className="flex justify-between gap-2 overflow-hidden">
<span className="shrink-0 whitespace-nowrap text-sm">
<b>{listItem.main}</b>
{feature.properties?._isCreation && (
<span className="bg-green-500 text-white text-[10px] px-1 rounded ml-1 font-normal">
Neu
</span>
)}
</span>
<span className="grow text-right whitespace-nowrap text-ellipsis overflow-hidden text-sm text-gray-700">
{listItem.upperright}
Expand Down
Loading
Loading