Compare commits
3 Commits
dbb60cac4b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d72cfb144 | |||
| 0bdb6f553b | |||
| f525279f2b |
@@ -22,9 +22,11 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
Link,
|
Link,
|
||||||
|
MenuItem,
|
||||||
Stack,
|
Stack,
|
||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography,
|
||||||
|
useMediaQuery
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { alpha, useTheme } from "@mui/material/styles";
|
import { alpha, useTheme } from "@mui/material/styles";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
@@ -153,10 +155,12 @@ export function BudgetColumn({
|
|||||||
}: BudgetColumnProps) {
|
}: BudgetColumnProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isDark = theme.palette.mode === "dark";
|
const isDark = theme.palette.mode === "dark";
|
||||||
|
const isCompactLayout = useMediaQuery(theme.breakpoints.down("lg"));
|
||||||
const [budgetDrafts, setBudgetDrafts] = useState<Record<string, BudgetDraft>>({});
|
const [budgetDrafts, setBudgetDrafts] = useState<Record<string, BudgetDraft>>({});
|
||||||
const [editingBudgetId, setEditingBudgetId] = useState<string | null>(null);
|
const [editingBudgetId, setEditingBudgetId] = useState<string | null>(null);
|
||||||
const [isEditingGroup, setIsEditingGroup] = useState(false);
|
const [isEditingGroup, setIsEditingGroup] = useState(false);
|
||||||
const [groupDraftName, setGroupDraftName] = useState(group.name);
|
const [groupDraftName, setGroupDraftName] = useState(group.name);
|
||||||
|
const [selectedBudgetId, setSelectedBudgetId] = useState(group.budgets[0]?.id ?? "");
|
||||||
const [proofFileDrafts, setProofFileDrafts] = useState<Record<string, { file: File; invoiceDate: string }[]>>({});
|
const [proofFileDrafts, setProofFileDrafts] = useState<Record<string, { file: File; invoiceDate: string }[]>>({});
|
||||||
const [expandedRecurringExpenses, setExpandedRecurringExpenses] = useState<Record<string, boolean>>({});
|
const [expandedRecurringExpenses, setExpandedRecurringExpenses] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
@@ -183,6 +187,12 @@ export function BudgetColumn({
|
|||||||
setGroupDraftName(group.name);
|
setGroupDraftName(group.name);
|
||||||
}, [group.name]);
|
}, [group.name]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!group.budgets.some((budget) => budget.id === selectedBudgetId)) {
|
||||||
|
setSelectedBudgetId(group.budgets[0]?.id ?? "");
|
||||||
|
}
|
||||||
|
}, [group.budgets, selectedBudgetId]);
|
||||||
|
|
||||||
const approvedSpend = useMemo(
|
const approvedSpend = useMemo(
|
||||||
() => group.budgets.reduce((sum, budget) => sum + getApprovedSpend(budget.expenses), 0),
|
() => group.budgets.reduce((sum, budget) => sum + getApprovedSpend(budget.expenses), 0),
|
||||||
[group.budgets]
|
[group.budgets]
|
||||||
@@ -251,6 +261,9 @@ export function BudgetColumn({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedBudget = group.budgets.find((budget) => budget.id === selectedBudgetId) ?? group.budgets[0] ?? null;
|
||||||
|
const visibleBudgets = isCompactLayout && selectedBudget ? [selectedBudget] : group.budgets;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
@@ -413,6 +426,22 @@ export function BudgetColumn({
|
|||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{isCompactLayout && group.budgets.length > 1 ? (
|
||||||
|
<TextField
|
||||||
|
select
|
||||||
|
label="Budget auswählen"
|
||||||
|
value={selectedBudget?.id ?? ""}
|
||||||
|
onChange={(event) => setSelectedBudgetId(event.target.value)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{group.budgets.map((budget) => (
|
||||||
|
<MenuItem key={budget.id} value={budget.id}>
|
||||||
|
{budget.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -420,11 +449,11 @@ export function BudgetColumn({
|
|||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
pb: 0,
|
pb: 0,
|
||||||
alignItems: "stretch",
|
alignItems: "stretch",
|
||||||
width: { md: desktopBudgetListWidth },
|
width: isCompactLayout ? "100%" : desktopBudgetListWidth,
|
||||||
minWidth: { md: desktopBudgetListWidth }
|
minWidth: isCompactLayout ? 0 : desktopBudgetListWidth
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{group.budgets.map((budget) => {
|
{visibleBudgets.map((budget) => {
|
||||||
const draft = getDraft(budget);
|
const draft = getDraft(budget);
|
||||||
const isEditing = editingBudgetId === budget.id;
|
const isEditing = editingBudgetId === budget.id;
|
||||||
const budgetApproved = getApprovedSpend(budget.expenses);
|
const budgetApproved = getApprovedSpend(budget.expenses);
|
||||||
@@ -444,9 +473,9 @@ export function BudgetColumn({
|
|||||||
<Box
|
<Box
|
||||||
key={budget.id}
|
key={budget.id}
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: { xs: 280, sm: 312, md: budgetCardWidth },
|
minWidth: isCompactLayout ? 0 : budgetCardWidth,
|
||||||
width: { xs: "84vw", sm: 312, md: budgetCardWidth },
|
width: isCompactLayout ? "100%" : budgetCardWidth,
|
||||||
maxWidth: { xs: 360, md: budgetCardWidth },
|
maxWidth: isCompactLayout ? "100%" : budgetCardWidth,
|
||||||
flex: "0 0 auto",
|
flex: "0 0 auto",
|
||||||
scrollSnapAlign: "start"
|
scrollSnapAlign: "start"
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -224,7 +224,8 @@ export async function uploadExpenseProofToDrive(input: {
|
|||||||
mimeType: input.mimeType,
|
mimeType: input.mimeType,
|
||||||
body: Readable.from(input.buffer)
|
body: Readable.from(input.buffer)
|
||||||
},
|
},
|
||||||
fields: "id, webViewLink"
|
fields: "id, name, webViewLink, webContentLink",
|
||||||
|
supportsAllDrives: true
|
||||||
}).catch((error: unknown) => {
|
}).catch((error: unknown) => {
|
||||||
throw mapDriveError(error, "DRIVE_UPLOAD_FAILED", "Google Drive konnte den Rechnungsbeleg nicht hochladen.", [
|
throw mapDriveError(error, "DRIVE_UPLOAD_FAILED", "Google Drive konnte den Rechnungsbeleg nicht hochladen.", [
|
||||||
`Zielordner: ${config.folderId}`,
|
`Zielordner: ${config.folderId}`,
|
||||||
@@ -243,7 +244,8 @@ export async function uploadExpenseProofToDrive(input: {
|
|||||||
requestBody: {
|
requestBody: {
|
||||||
type: "anyone",
|
type: "anyone",
|
||||||
role: "reader"
|
role: "reader"
|
||||||
}
|
},
|
||||||
|
supportsAllDrives: true
|
||||||
}).catch((error: unknown) => {
|
}).catch((error: unknown) => {
|
||||||
throw mapDriveError(error, "DRIVE_PERMISSION_FAILED", "Google Drive konnte den Freigabe-Link nicht erstellen.", [
|
throw mapDriveError(error, "DRIVE_PERMISSION_FAILED", "Google Drive konnte den Freigabe-Link nicht erstellen.", [
|
||||||
`Die Datei wurde vermutlich bereits erstellt. Drive-Datei-ID: ${response.data.id}`
|
`Die Datei wurde vermutlich bereits erstellt. Drive-Datei-ID: ${response.data.id}`
|
||||||
@@ -273,7 +275,8 @@ export async function runDriveDiagnostics() {
|
|||||||
mimeType: "text/plain",
|
mimeType: "text/plain",
|
||||||
body: Readable.from(Buffer.from("RFP Finanzen Drive API Test\n", "utf8"))
|
body: Readable.from(Buffer.from("RFP Finanzen Drive API Test\n", "utf8"))
|
||||||
},
|
},
|
||||||
fields: "id, webViewLink"
|
fields: "id, name, webViewLink, webContentLink",
|
||||||
|
supportsAllDrives: true
|
||||||
});
|
});
|
||||||
|
|
||||||
createdFileId = response.data.id ?? null;
|
createdFileId = response.data.id ?? null;
|
||||||
@@ -282,7 +285,7 @@ export async function runDriveDiagnostics() {
|
|||||||
throw new DriveIntegrationError("Google Drive hat für die Testdatei keine Datei-ID zurückgegeben.", "DRIVE_FILE_ID_MISSING");
|
throw new DriveIntegrationError("Google Drive hat für die Testdatei keine Datei-ID zurückgegeben.", "DRIVE_FILE_ID_MISSING");
|
||||||
}
|
}
|
||||||
|
|
||||||
await drive.files.delete({ fileId: createdFileId });
|
await drive.files.delete({ fileId: createdFileId, supportsAllDrives: true });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -296,7 +299,7 @@ export async function runDriveDiagnostics() {
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (createdFileId) {
|
if (createdFileId) {
|
||||||
await drive.files.delete({ fileId: createdFileId }).catch((cleanupError: unknown) => {
|
await drive.files.delete({ fileId: createdFileId, supportsAllDrives: true }).catch((cleanupError: unknown) => {
|
||||||
throw new DriveIntegrationError(
|
throw new DriveIntegrationError(
|
||||||
"Drive-Test ist fehlgeschlagen und die temporäre Testdatei konnte nicht gelöscht werden.",
|
"Drive-Test ist fehlgeschlagen und die temporäre Testdatei konnte nicht gelöscht werden.",
|
||||||
"DRIVE_DIAGNOSTIC_CLEANUP_FAILED",
|
"DRIVE_DIAGNOSTIC_CLEANUP_FAILED",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
@@ -26,4 +25,3 @@
|
|||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user