Edit Draft Mission

This commit is contained in:
Nicolas
2025-04-28 09:50:58 +02:00
parent e1c3f51809
commit 64fcab59af
5 changed files with 126 additions and 45 deletions

View File

@@ -7,11 +7,18 @@ interface PannelStore {
setOpen: (isOpen: boolean) => void; setOpen: (isOpen: boolean) => void;
missionFormValues?: Partial<MissionOptionalDefaults>; missionFormValues?: Partial<MissionOptionalDefaults>;
setMissionFormValues: (values: Partial<MissionOptionalDefaults>) => void; setMissionFormValues: (values: Partial<MissionOptionalDefaults>) => void;
isEditingMission: boolean;
editingMissionId: string | null;
setEditingMission: (isEditing: boolean, missionId: string | null) => void;
} }
export const usePannelStore = create<PannelStore>((set) => ({ export const usePannelStore = create<PannelStore>((set) => ({
isOpen: false, // DEBUG, REMOVE LATER FOR PROD isOpen: false,
setOpen: (isOpen) => set({ isOpen }), setOpen: (isOpen) => set({ isOpen }),
missionFormValues: undefined, missionFormValues: undefined,
setMissionFormValues: (values) => set({ missionFormValues: values }), setMissionFormValues: (values) => set({ missionFormValues: values }),
isEditingMission: false,
editingMissionId: null,
setEditingMission: (isEditing, missionId) =>
set({ isEditingMission: isEditing, editingMissionId: missionId }),
})); }));

View File

@@ -2,6 +2,7 @@ import { useMissionsStore } from "_store/missionsStore";
import { Marker, useMap } from "react-leaflet"; import { Marker, useMap } from "react-leaflet";
import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet"; import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
import { useMapStore } from "_store/mapStore"; import { useMapStore } from "_store/mapStore";
import { usePannelStore } from "_store/pannelStore";
import { import {
Fragment, Fragment,
useCallback, useCallback,
@@ -18,6 +19,7 @@ import {
Minimize2, Minimize2,
Route, Route,
SmartphoneNfc, SmartphoneNfc,
PencilLine,
} from "lucide-react"; } from "lucide-react";
import { import {
calculateAnchor, calculateAnchor,
@@ -35,6 +37,7 @@ export const MISSION_STATUS_COLORS: Record<MissionState, string> = {
draft: "#0092b8", draft: "#0092b8",
running: "#155dfc", running: "#155dfc",
finished: "#155dfc", finished: "#155dfc",
attention: "rgb(186,105,0)",
}; };
export const MISSION_STATUS_TEXT_COLORS: Record<MissionState, string> = { export const MISSION_STATUS_TEXT_COLORS: Record<MissionState, string> = {
@@ -44,6 +47,7 @@ export const MISSION_STATUS_TEXT_COLORS: Record<MissionState, string> = {
}; };
const MissionPopupContent = ({ mission }: { mission: Mission }) => { const MissionPopupContent = ({ mission }: { mission: Mission }) => {
const { setEditingMission } = usePannelStore();
const setMissionMarker = useMapStore((state) => state.setOpenMissionMarker); const setMissionMarker = useMapStore((state) => state.setOpenMissionMarker);
const currentTab = useMapStore( const currentTab = useMapStore(
(state) => (state) =>
@@ -85,6 +89,7 @@ const MissionPopupContent = ({ mission }: { mission: Mission }) => {
(state) => state.setOpenMissionMarker, (state) => state.setOpenMissionMarker,
); );
const { anchor } = useSmartPopup(); const { anchor } = useSmartPopup();
const { setMissionFormValues, setOpen } = usePannelStore((state) => state);
return ( return (
<> <>
@@ -167,8 +172,30 @@ const MissionPopupContent = ({ mission }: { mission: Mission }) => {
> >
<SmartphoneNfc className="text-sm" /> <SmartphoneNfc className="text-sm" />
</div> </div>
{mission.state === "draft" && (
<div
className="p-2 px-4 flex justify-center items-center cursor-pointer ml-auto"
style={{
backgroundColor: `${MISSION_STATUS_COLORS["attention"]}`,
borderBottom: "5px solid transparent",
}}
onClick={() => {
setMissionFormValues({
...mission,
state: "draft",
});
setEditingMission(true, mission.id);
setOpen(true);
}}
>
<PencilLine className="text-sm" />
</div>
)}
<div <div
className="p-2 px-4 flex justify-center items-center cursor-pointer ml-auto" className={cn(
"p-2 px-4 flex justify-center items-center cursor-pointer",
mission.state !== "draft" && "ml-auto",
)}
style={{ style={{
backgroundColor: `${MISSION_STATUS_COLORS[mission.state]}`, backgroundColor: `${MISSION_STATUS_COLORS[mission.state]}`,
borderBottom: borderBottom:

View File

@@ -28,7 +28,7 @@ import { useSession } from "next-auth/react";
const Einsatzdetails = ({ mission }: { mission: Mission }) => { const Einsatzdetails = ({ mission }: { mission: Mission }) => {
const { deleteMission } = useMissionsStore((state) => state); const { deleteMission } = useMissionsStore((state) => state);
const { setMissionFormValues } = usePannelStore((state) => state); const { setMissionFormValues, setOpen } = usePannelStore((state) => state);
return ( return (
<div className="p-4 text-base-content"> <div className="p-4 text-base-content">
<h2 className="flex items-center gap-2 text-lg font-bold mb-3"> <h2 className="flex items-center gap-2 text-lg font-bold mb-3">
@@ -81,6 +81,7 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
hpgLocationLng: undefined, hpgLocationLng: undefined,
state: "draft", state: "draft",
}); });
setOpen(true);
}} }}
> >
<Repeat2 size={18} /> Daten übernehmen <Repeat2 size={18} /> Daten übernehmen

View File

@@ -16,7 +16,10 @@ import { toast } from "react-hot-toast";
import { useMissionsStore } from "_store/missionsStore"; import { useMissionsStore } from "_store/missionsStore";
export const MissionForm = () => { export const MissionForm = () => {
const { isEditingMission, editingMissionId, setEditingMission } =
usePannelStore();
const createMission = useMissionsStore((state) => state.createMission); const createMission = useMissionsStore((state) => state.createMission);
const { deleteMission } = useMissionsStore((state) => state);
const session = useSession(); const session = useSession();
const defaultFormValues = React.useMemo( const defaultFormValues = React.useMemo(
() => () =>
@@ -41,7 +44,7 @@ export const MissionForm = () => {
resolver: zodResolver(MissionOptionalDefaultsSchema), resolver: zodResolver(MissionOptionalDefaultsSchema),
defaultValues: defaultFormValues, defaultValues: defaultFormValues,
}); });
const { missionFormValues } = usePannelStore((state) => state); const { missionFormValues, setOpen } = usePannelStore((state) => state);
useEffect(() => { useEffect(() => {
if (session.data?.user.id) { if (session.data?.user.id) {
@@ -290,44 +293,75 @@ export const MissionForm = () => {
<div className="form-control min-h-[140px] max-w-[320px]"> <div className="form-control min-h-[140px] max-w-[320px]">
<div className="flex gap-2"> <div className="flex gap-2">
<button {isEditingMission && editingMissionId ? (
type="submit" <button
className="btn btn-warning" type="button"
onClick={form.handleSubmit( className="btn btn-primary btn-block"
async (mission: MissionOptionalDefaults) => { onClick={form.handleSubmit(
try { async (mission: MissionOptionalDefaults) => {
const newMission = await createMission(mission); try {
toast.success(`Einsatz ${newMission.id} erstellt`); deleteMission(editingMissionId);
// TODO: Einsatz alarmieren const newMission = await createMission(mission);
} catch (error) { toast.success(
toast.error( `Einsatz ${newMission.id} erfolgreich aktualisiert`,
`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`, );
); setEditingMission(false, null); // Reset editing state
} form.reset(); // Reset the form
}, setOpen(false);
)} } catch (error) {
> toast.error(
<BellRing className="h-4 w-4" /> Alarmieren `Fehler beim Aktualisieren des Einsatzes: ${(error as Error).message}`,
</button> );
<button }
type="submit" },
className="btn btn-primary btn-block" )}
onClick={form.handleSubmit( >
async (mission: MissionOptionalDefaults) => { Änderungen speichern
try { </button>
const newMission = await createMission(mission); ) : (
toast.success(`Einsatz ${newMission.id} erstellt`); <>
form.reset(); <button
} catch (error) { type="submit"
toast.error( className="btn btn-warning"
`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`, onClick={form.handleSubmit(
); async (mission: MissionOptionalDefaults) => {
} try {
}, const newMission = await createMission(mission);
)} toast.success(`Einsatz ${newMission.id} erstellt`);
> // TODO: Einsatz alarmieren
<BookmarkPlus className="h-5 w-5" /> Einsatz vorbereiten setOpen(false);
</button> } catch (error) {
toast.error(
`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`,
);
}
},
)}
>
<BellRing className="h-4 w-4" /> Alarmieren
</button>
<button
type="submit"
className="btn btn-primary btn-block"
onClick={form.handleSubmit(
async (mission: MissionOptionalDefaults) => {
try {
const newMission = await createMission(mission);
toast.success(`Einsatz ${newMission.id} erstellt`);
form.reset();
setOpen(false);
} catch (error) {
toast.error(
`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`,
);
}
},
)}
>
<BookmarkPlus className="h-5 w-5" /> Einsatz vorbereiten
</button>
</>
)}
</div> </div>
</div> </div>
</form> </form>

View File

@@ -5,22 +5,34 @@ import { Rss, Trash2Icon } from "lucide-react";
export const Pannel = () => { export const Pannel = () => {
const { setOpen, setMissionFormValues } = usePannelStore(); const { setOpen, setMissionFormValues } = usePannelStore();
const { isEditingMission, editingMissionId, setEditingMission } =
usePannelStore();
return ( return (
<div className={cn("flex-1 max-w-[600px] z-9999999")}> <div className={cn("flex-1 max-w-[600px] z-9999999")}>
<div className="bg-base-100 min-h-screen h-full max-h-screen w-full overflow-auto"> <div className="bg-base-100 min-h-screen h-full max-h-screen w-full overflow-auto">
<div className="flex flex-row justify-between items-center p-4"> <div className="flex flex-row justify-between items-center p-4">
<h1 className="text-xl font-bold flex items-center gap-2"> <h1 className="text-xl font-bold flex items-center gap-2">
<Rss /> Neuer Einsatz <Rss /> {isEditingMission ? "Einsatz bearbeiten" : "Neuer Einsatz"}
</h1> </h1>
<div> <div>
<button <button
className="btn btn-ghost btn-sm mr-2 btn-warning" className="btn btn-ghost btn-sm mr-2 btn-warning"
onClick={() => setMissionFormValues({})} onClick={() => {
setMissionFormValues({});
setEditingMission(false, null);
}}
> >
<Trash2Icon size={18} /> <Trash2Icon size={18} />
</button> </button>
<button className="btn" onClick={() => setOpen(false)}> <button
className="btn"
onClick={() => {
setOpen(false);
setEditingMission(false, null);
setMissionFormValues({});
}}
>
Abbrechen Abbrechen
</button> </button>
</div> </div>