featuer change for calendar
This commit is contained in:
@@ -78,7 +78,7 @@ class BookingService {
|
|||||||
SELECT
|
SELECT
|
||||||
b.id, b.fahrzeug_id, b.titel, b.buchungs_art::text AS buchungs_art,
|
b.id, b.fahrzeug_id, b.titel, b.buchungs_art::text AS buchungs_art,
|
||||||
b.beginn, b.ende, b.abgesagt,
|
b.beginn, b.ende, b.abgesagt,
|
||||||
f.name AS fahrzeug_name, f.kennzeichen AS fahrzeug_kennzeichen,
|
f.bezeichnung AS fahrzeug_name, f.amtliches_kennzeichen AS fahrzeug_kennzeichen,
|
||||||
u.name AS gebucht_von_name
|
u.name AS gebucht_von_name
|
||||||
FROM fahrzeug_buchungen b
|
FROM fahrzeug_buchungen b
|
||||||
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
||||||
@@ -103,7 +103,7 @@ class BookingService {
|
|||||||
SELECT
|
SELECT
|
||||||
b.id, b.fahrzeug_id, b.titel, b.buchungs_art::text AS buchungs_art,
|
b.id, b.fahrzeug_id, b.titel, b.buchungs_art::text AS buchungs_art,
|
||||||
b.beginn, b.ende, b.abgesagt,
|
b.beginn, b.ende, b.abgesagt,
|
||||||
f.name AS fahrzeug_name, f.kennzeichen AS fahrzeug_kennzeichen,
|
f.bezeichnung AS fahrzeug_name, f.amtliches_kennzeichen AS fahrzeug_kennzeichen,
|
||||||
u.name AS gebucht_von_name
|
u.name AS gebucht_von_name
|
||||||
FROM fahrzeug_buchungen b
|
FROM fahrzeug_buchungen b
|
||||||
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
||||||
@@ -128,7 +128,7 @@ class BookingService {
|
|||||||
b.gebucht_von, b.kontakt_person, b.kontakt_telefon,
|
b.gebucht_von, b.kontakt_person, b.kontakt_telefon,
|
||||||
b.abgesagt, b.abgesagt_grund,
|
b.abgesagt, b.abgesagt_grund,
|
||||||
b.erstellt_am, b.aktualisiert_am,
|
b.erstellt_am, b.aktualisiert_am,
|
||||||
f.name AS fahrzeug_name, f.kennzeichen AS fahrzeug_kennzeichen,
|
f.bezeichnung AS fahrzeug_name, f.amtliches_kennzeichen AS fahrzeug_kennzeichen,
|
||||||
u.name AS gebucht_von_name
|
u.name AS gebucht_von_name
|
||||||
FROM fahrzeug_buchungen b
|
FROM fahrzeug_buchungen b
|
||||||
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
||||||
@@ -345,7 +345,7 @@ class BookingService {
|
|||||||
SELECT
|
SELECT
|
||||||
b.id, b.titel, b.beschreibung, b.buchungs_art::text AS buchungs_art,
|
b.id, b.titel, b.beschreibung, b.buchungs_art::text AS buchungs_art,
|
||||||
b.beginn, b.ende,
|
b.beginn, b.ende,
|
||||||
f.name AS fahrzeug_name
|
f.bezeichnung AS fahrzeug_name
|
||||||
FROM fahrzeug_buchungen b
|
FROM fahrzeug_buchungen b
|
||||||
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
JOIN fahrzeuge f ON f.id = b.fahrzeug_id
|
||||||
WHERE b.abgesagt = FALSE
|
WHERE b.abgesagt = FALSE
|
||||||
|
|||||||
@@ -423,11 +423,11 @@ function FahrzeugBuchungen() {
|
|||||||
<TableRow key={vehicle.id} hover>
|
<TableRow key={vehicle.id} hover>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography variant="body2" fontWeight={600}>
|
<Typography variant="body2" fontWeight={600}>
|
||||||
{vehicle.name}
|
{vehicle.bezeichnung}
|
||||||
</Typography>
|
</Typography>
|
||||||
{vehicle.kennzeichen && (
|
{vehicle.amtliches_kennzeichen && (
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{vehicle.kennzeichen}
|
{vehicle.amtliches_kennzeichen}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -632,8 +632,8 @@ function FahrzeugBuchungen() {
|
|||||||
>
|
>
|
||||||
{vehicles.map((v) => (
|
{vehicles.map((v) => (
|
||||||
<MenuItem key={v.id} value={v.id}>
|
<MenuItem key={v.id} value={v.id}>
|
||||||
{v.name}
|
{v.bezeichnung}
|
||||||
{v.kennzeichen ? ` (${v.kennzeichen})` : ''}
|
{v.amtliches_kennzeichen ? ` (${v.amtliches_kennzeichen})` : ''}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import {
|
|||||||
Edit as EditIcon,
|
Edit as EditIcon,
|
||||||
Event as EventIcon,
|
Event as EventIcon,
|
||||||
HelpOutline as UnknownIcon,
|
HelpOutline as UnknownIcon,
|
||||||
|
IosShare,
|
||||||
Star as StarIcon,
|
Star as StarIcon,
|
||||||
Today as TodayIcon,
|
Today as TodayIcon,
|
||||||
Tune,
|
Tune,
|
||||||
@@ -1085,6 +1086,12 @@ export default function Kalender() {
|
|||||||
const [cancelBookingGrund, setCancelBookingGrund] = useState('');
|
const [cancelBookingGrund, setCancelBookingGrund] = useState('');
|
||||||
const [cancelBookingLoading, setCancelBookingLoading] = useState(false);
|
const [cancelBookingLoading, setCancelBookingLoading] = useState(false);
|
||||||
|
|
||||||
|
// iCal subscription
|
||||||
|
const [icalEventOpen, setIcalEventOpen] = useState(false);
|
||||||
|
const [icalEventUrl, setIcalEventUrl] = useState('');
|
||||||
|
const [icalBookingOpen, setIcalBookingOpen] = useState(false);
|
||||||
|
const [icalBookingUrl, setIcalBookingUrl] = useState('');
|
||||||
|
|
||||||
// ── Data loading ─────────────────────────────────────────────────────────────
|
// ── Data loading ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const loadCalendarData = useCallback(async () => {
|
const loadCalendarData = useCallback(async () => {
|
||||||
@@ -1354,6 +1361,28 @@ export default function Kalender() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleIcalEventOpen = async () => {
|
||||||
|
try {
|
||||||
|
const { subscribeUrl } = await eventsApi.getCalendarToken();
|
||||||
|
setIcalEventUrl(subscribeUrl);
|
||||||
|
setIcalEventOpen(true);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const msg = e instanceof Error ? e.message : 'Fehler beim Laden des Tokens';
|
||||||
|
notification.showError(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIcalBookingOpen = async () => {
|
||||||
|
try {
|
||||||
|
const { subscribeUrl } = await bookingApi.getCalendarToken();
|
||||||
|
setIcalBookingUrl(subscribeUrl);
|
||||||
|
setIcalBookingOpen(true);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const msg = e instanceof Error ? e.message : 'Fehler beim Laden des Tokens';
|
||||||
|
notification.showError(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ── Render ───────────────────────────────────────────────────────────────────
|
// ── Render ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1451,6 +1480,16 @@ export default function Kalender() {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* iCal subscribe */}
|
||||||
|
<Button
|
||||||
|
startIcon={<IosShare />}
|
||||||
|
onClick={handleIcalEventOpen}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Kalender
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Month navigation */}
|
{/* Month navigation */}
|
||||||
@@ -1594,6 +1633,37 @@ export default function Kalender() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* iCal Event subscription dialog */}
|
||||||
|
<Dialog open={icalEventOpen} onClose={() => setIcalEventOpen(false)} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>Kalender abonnieren</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
Abonniere den Dienste- & Veranstaltungskalender in deiner Kalenderanwendung. Kopiere die URL und füge sie als neuen
|
||||||
|
Kalender (per URL) in Apple Kalender, Google Kalender oder Outlook ein.
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
value={icalEventUrl}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(icalEventUrl);
|
||||||
|
notification.showSuccess('URL kopiert!');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyIcon />
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setIcalEventOpen(false)}>Schließen</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1649,6 +1719,16 @@ export default function Kalender() {
|
|||||||
Neue Buchung
|
Neue Buchung
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* iCal subscribe */}
|
||||||
|
<Button
|
||||||
|
startIcon={<IosShare />}
|
||||||
|
onClick={handleIcalBookingOpen}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Kalender
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{bookingsLoading && (
|
{bookingsLoading && (
|
||||||
@@ -1696,11 +1776,11 @@ export default function Kalender() {
|
|||||||
<TableRow key={vehicle.id} hover>
|
<TableRow key={vehicle.id} hover>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography variant="body2" fontWeight={600}>
|
<Typography variant="body2" fontWeight={600}>
|
||||||
{vehicle.name}
|
{vehicle.bezeichnung}
|
||||||
</Typography>
|
</Typography>
|
||||||
{vehicle.kennzeichen && (
|
{vehicle.amtliches_kennzeichen && (
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{vehicle.kennzeichen}
|
{vehicle.amtliches_kennzeichen}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -1900,7 +1980,7 @@ export default function Kalender() {
|
|||||||
>
|
>
|
||||||
{vehicles.map((v) => (
|
{vehicles.map((v) => (
|
||||||
<MenuItem key={v.id} value={v.id}>
|
<MenuItem key={v.id} value={v.id}>
|
||||||
{v.name}{v.kennzeichen ? ` (${v.kennzeichen})` : ''}
|
{v.bezeichnung}{v.amtliches_kennzeichen ? ` (${v.amtliches_kennzeichen})` : ''}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -2041,6 +2121,37 @@ export default function Kalender() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* iCal Booking subscription dialog */}
|
||||||
|
<Dialog open={icalBookingOpen} onClose={() => setIcalBookingOpen(false)} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>Kalender abonnieren</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
Abonniere den Fahrzeugbuchungskalender in deiner Kalenderanwendung. Kopiere die URL und füge sie als neuen
|
||||||
|
Kalender (per URL) in Apple Kalender, Google Kalender oder Outlook ein.
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
value={icalBookingUrl}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(icalBookingUrl);
|
||||||
|
notification.showSuccess('URL kopiert!');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyIcon />
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setIcalBookingOpen(false)}>Schließen</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -113,5 +113,5 @@ export const bookingApi = {
|
|||||||
export function fetchVehicles(): Promise<Fahrzeug[]> {
|
export function fetchVehicles(): Promise<Fahrzeug[]> {
|
||||||
return api
|
return api
|
||||||
.get<ApiResponse<Fahrzeug[]>>('/api/vehicles')
|
.get<ApiResponse<Fahrzeug[]>>('/api/vehicles')
|
||||||
.then((r) => r.data.data.filter((v: Fahrzeug) => !v.archived_at));
|
.then((r) => r.data.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,10 @@ export interface FahrzeugBuchung extends FahrzeugBuchungListItem {
|
|||||||
|
|
||||||
export interface Fahrzeug {
|
export interface Fahrzeug {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
bezeichnung: string;
|
||||||
kennzeichen?: string | null;
|
kurzname: string | null;
|
||||||
typ: string;
|
amtliches_kennzeichen: string | null;
|
||||||
is_active: boolean;
|
status: string;
|
||||||
archived_at?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateBuchungInput {
|
export interface CreateBuchungInput {
|
||||||
|
|||||||
Reference in New Issue
Block a user