mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-20 13:38:08 +02:00
fix: resolve EACCES permission issue and hydration error #418
This commit is contained in:
1 parent
0026e3134d
commit
4f4a0dbd62
4 files changed
+45
-13
No files matched your search
+6
-3
@@ -37,15 +37,18 @@ RUN adduser --system --uid 1001 nextjs
|
|||||||
|
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
# Set the correct permission for prerender cache
|
# Set the correct permission for prerender cache and data storage
|
||||||
RUN mkdir .next
|
RUN mkdir .next data
|
||||||
RUN chown nextjs:nodejs .next
|
RUN chown nextjs:nodejs .next data
|
||||||
|
|
||||||
# Automatically leverage output traces to reduce image size
|
# Automatically leverage output traces to reduce image size
|
||||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
# Grant write access to /app for settings persistence
|
||||||
|
RUN chown -R nextjs:nodejs /app
|
||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
|
|
||||||
EXPOSE 5643
|
EXPOSE 5643
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ let connectionString = process.env.DATABASE_URL;
|
|||||||
|
|
||||||
// On tente de lire la config sauvegardée via l'UI
|
// On tente de lire la config sauvegardée via l'UI
|
||||||
try {
|
try {
|
||||||
const CONFIG_PATH = path.join(process.cwd(), ".db-config.json");
|
const CONFIG_PATH = path.join(process.cwd(), "data", ".db-config.json");
|
||||||
if (fs.existsSync(CONFIG_PATH)) {
|
if (fs.existsSync(CONFIG_PATH)) {
|
||||||
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
||||||
if (config.url) {
|
if (config.url) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function getLast12Months(): string[] {
|
|||||||
return months;
|
return months;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MONTHS_12 = getLast12Months();
|
// La constante est supprimée d'ici pour éviter le mismatch d'hydratation (new Date() au runtime module)
|
||||||
|
|
||||||
function formatMonthLabel(key: string): string {
|
function formatMonthLabel(key: string): string {
|
||||||
const m = parseInt(key.slice(4, 6), 10);
|
const m = parseInt(key.slice(4, 6), 10);
|
||||||
@@ -47,10 +47,17 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
const { rows, draftChanges, setDraftGamme, filters, displayDensity } = useGridStore();
|
const { rows, draftChanges, setDraftGamme, filters, displayDensity } = useGridStore();
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Calculer les mois dynamiquement pour éviter le mismatch entre serveur et client
|
||||||
|
const MONTHS_12 = useMemo(() => getLast12Months(), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const rowHeight = displayDensity === "compact" ? 32 : displayDensity === "normal" ? 40 : 48;
|
const rowHeight = displayDensity === "compact" ? 32 : displayDensity === "normal" ? 40 : 48;
|
||||||
const cellPadding = displayDensity === "compact" ? "py-1" : displayDensity === "normal" ? "py-1.5" : "py-2.5";
|
|
||||||
|
|
||||||
const handleRowSelectionChange = useCallback(
|
const handleRowSelectionChange = useCallback(
|
||||||
(updater: React.SetStateAction<RowSelectionState>) => {
|
(updater: React.SetStateAction<RowSelectionState>) => {
|
||||||
@@ -241,6 +248,21 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
|
|
||||||
const totalWidth = columns.reduce((s, c) => s + ((c as { size?: number }).size ?? 150), 0);
|
const totalWidth = columns.reduce((s, c) => s + ((c as { size?: number }).size ?? 150), 0);
|
||||||
|
|
||||||
|
if (!isMounted) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="overflow-auto rounded-[12px] flex items-center justify-center"
|
||||||
|
style={{
|
||||||
|
height: "calc(100vh - 240px)",
|
||||||
|
background: "var(--bg-surface)",
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-muted text-sm italic opacity-50">Chargement de la grille...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={tableContainerRef}
|
ref={tableContainerRef}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { Pool } from "pg";
|
|||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
const CONFIG_FILE = path.join(process.cwd(), ".db-config.json");
|
const DATA_DIR = path.join(process.cwd(), "data");
|
||||||
|
const CONFIG_FILE = path.join(DATA_DIR, ".db-config.json");
|
||||||
|
|
||||||
export interface DbConfig {
|
export interface DbConfig {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -22,9 +23,10 @@ export async function testDatabaseConnection(url: string) {
|
|||||||
await client.query("SELECT 1");
|
await client.query("SELECT 1");
|
||||||
client.release();
|
client.release();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error("Database connection test failed:", error.message);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
return { success: false, error: error.message };
|
console.error("Database connection test failed:", errorMessage);
|
||||||
|
return { success: false, error: errorMessage };
|
||||||
} finally {
|
} finally {
|
||||||
await pool.end();
|
await pool.end();
|
||||||
}
|
}
|
||||||
@@ -33,12 +35,17 @@ export async function testDatabaseConnection(url: string) {
|
|||||||
export async function saveDatabaseSettings(url: string) {
|
export async function saveDatabaseSettings(url: string) {
|
||||||
try {
|
try {
|
||||||
const config: DbConfig = { url };
|
const config: DbConfig = { url };
|
||||||
|
|
||||||
|
// S'assurer que le dossier data existe
|
||||||
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
|
|
||||||
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
||||||
console.log("Database configuration saved to:", CONFIG_FILE);
|
console.log("Database configuration saved to:", CONFIG_FILE);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error("Failed to save database configuration:", error.message);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
return { success: false, error: error.message };
|
console.error("Failed to save database configuration:", errorMessage);
|
||||||
|
return { success: false, error: errorMessage };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user