From b86bd6fbc298a28a9a4ccf3b268b89c178b2bfec Mon Sep 17 00:00:00 2001 From: neckfire Date: Sat, 15 Aug 2026 20:40:30 +0200 Subject: [PATCH] =?UTF-8?q?R=C3=A9tablit=20les=20fins=20de=20ligne=20CRLF?= =?UTF-8?q?=20sur=20les=20fichiers=20du=20commit=20pr=C3=A9c=C3=A9dent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le script d'édition du commit précédent a réécrit ces cinq fichiers en LF alors que le dépôt les stocke en CRLF, ce qui gonflait le diff au fichier entier et aurait rendu les prochains merges pénibles. Contenu strictement identique, seules les fins de ligne reviennent à la convention du dépôt. Co-Authored-By: Claude Opus 5 (1M context) --- src/pages/Documents.tsx | 516 +++++++++++----------- src/pages/IngredientSearchPage.tsx | 382 ++++++++-------- src/pages/RecentUpdatesPage.tsx | 422 +++++++++--------- src/styles/DocumentsPage.module.css | 474 ++++++++++---------- src/styles/RecentUpdatesPage.module.css | 556 ++++++++++++------------ 5 files changed, 1175 insertions(+), 1175 deletions(-) diff --git a/src/pages/Documents.tsx b/src/pages/Documents.tsx index d7a0981..0a95231 100644 --- a/src/pages/Documents.tsx +++ b/src/pages/Documents.tsx @@ -1,259 +1,259 @@ -import { useEffect, useMemo, useState } from "react"; -import { - Filter, - ArrowUpDown, - File as FileIcon, -} from "lucide-react"; - -import styles from "../styles/DocumentsPage.module.css"; -import Pagination from "../components/Pagination"; -import { getDocuments } from "../services/theapi"; - -type ApiDocument = { - title: string; - ingredient: string; - source: string; - type: string; - date?: string | null; - pdf_url: string; - bold?: boolean; -}; - -const DEFAULT_ITEMS_PER_PAGE = 8; - -export default function DocumentsPage() { - const [documents, setDocuments] = useState([]); - const [loading, setLoading] = useState(true); - const [itemsPerPage, setItemsPerPage] = useState(DEFAULT_ITEMS_PER_PAGE); - - const [sourceFilter, setSourceFilter] = useState("All Sources"); - const [typeFilter, setTypeFilter] = useState("All Types"); - const [dateFilter, setDateFilter] = useState("All Dates"); - const [sortBy, setSortBy] = useState("Date (Newest)"); - const [currentPage, setCurrentPage] = useState(1); - - useEffect(() => { - getDocuments() - .then((data) => { - setDocuments(Array.isArray(data) ? data : []); - }) - .catch((err) => { - console.error("API Error:", err); - setDocuments([]); - }) - .finally(() => setLoading(false)); - }, []); - - const allSources = useMemo( - () => ["All Sources", ...new Set(documents.map((d) => d.source))], - [documents] - ); - - const allTypes = useMemo( - () => ["All Types", ...new Set(documents.map((d) => d.type))], - [documents] - ); - - const allDates = useMemo( - () => [ - "All Dates", - ...new Set( - documents.map((d) => String(d.date ?? "").slice(0, 4)) - ), - ], - [documents] - ); - - const filtered = useMemo(() => { - return documents - .filter((d) => { - const sourceMatch = - sourceFilter === "All Sources" || d.source === sourceFilter; - - const typeMatch = - typeFilter === "All Types" || d.type === typeFilter; - - const safeDate = String(d.date ?? ""); - const dateMatch = - dateFilter === "All Dates" || - safeDate.startsWith(dateFilter); - - return sourceMatch && typeMatch && dateMatch; - }) - .sort((a, b) => { - const dateA = String(a.date ?? ""); - const dateB = String(b.date ?? ""); - const titleA = String(a.title ?? ""); - const titleB = String(b.title ?? ""); - - if (sortBy === "Date (Newest)") return dateB.localeCompare(dateA); - if (sortBy === "Date (Oldest)") return dateA.localeCompare(dateB); - if (sortBy === "Title (A-Z)") return titleA.localeCompare(titleB); - if (sortBy === "Title (Z-A)") return titleB.localeCompare(titleA); - return 0; - }); - }, [documents, sourceFilter, typeFilter, dateFilter, sortBy]); - - const totalPages = Math.max( - 1, - Math.ceil(filtered.length / itemsPerPage) - ); - - const safePage = Math.min(currentPage, totalPages); - - const paginated = filtered.slice( - (safePage - 1) * itemsPerPage, - safePage * itemsPerPage - ); - - // adjust items per page based on viewport width - useEffect(() => { - const apply = () => { - const w = window.innerWidth; - if (w < 640) setItemsPerPage(3); - else if (w < 900) setItemsPerPage(6); - else setItemsPerPage(DEFAULT_ITEMS_PER_PAGE); - }; - apply(); - window.addEventListener('resize', apply); - return () => window.removeEventListener('resize', apply); - }, []); - - if (loading) { - return ( -
-

Loading documents...

-
- ); - } - - return ( -
-
- {/* HEADER */} -
-

Document Database

-

- Complete collection of regulatory monitoring documents -

-
- - {/* FILTERS */} -
-
- - Filters: -
- - - - - - - -
- - -
-
- - {/* STATS */} -
-

- Total Documents: - - {" "} - {filtered.length} - -

-
- - {/* TABLE */} -
-
- Title - Ingredient - Source - Type - Date - PDF -
- - {paginated.map((doc, index) => ( -
-
- - {doc.title} -
- - {doc.ingredient} - {doc.source} - {doc.type} - {doc.date} - - -
- ))} -
- - -
-
- ); +import { useEffect, useMemo, useState } from "react"; +import { + Filter, + ArrowUpDown, + File as FileIcon, +} from "lucide-react"; + +import styles from "../styles/DocumentsPage.module.css"; +import Pagination from "../components/Pagination"; +import { getDocuments } from "../services/theapi"; + +type ApiDocument = { + title: string; + ingredient: string; + source: string; + type: string; + date?: string | null; + pdf_url: string; + bold?: boolean; +}; + +const DEFAULT_ITEMS_PER_PAGE = 8; + +export default function DocumentsPage() { + const [documents, setDocuments] = useState([]); + const [loading, setLoading] = useState(true); + const [itemsPerPage, setItemsPerPage] = useState(DEFAULT_ITEMS_PER_PAGE); + + const [sourceFilter, setSourceFilter] = useState("All Sources"); + const [typeFilter, setTypeFilter] = useState("All Types"); + const [dateFilter, setDateFilter] = useState("All Dates"); + const [sortBy, setSortBy] = useState("Date (Newest)"); + const [currentPage, setCurrentPage] = useState(1); + + useEffect(() => { + getDocuments() + .then((data) => { + setDocuments(Array.isArray(data) ? data : []); + }) + .catch((err) => { + console.error("API Error:", err); + setDocuments([]); + }) + .finally(() => setLoading(false)); + }, []); + + const allSources = useMemo( + () => ["All Sources", ...new Set(documents.map((d) => d.source))], + [documents] + ); + + const allTypes = useMemo( + () => ["All Types", ...new Set(documents.map((d) => d.type))], + [documents] + ); + + const allDates = useMemo( + () => [ + "All Dates", + ...new Set( + documents.map((d) => String(d.date ?? "").slice(0, 4)) + ), + ], + [documents] + ); + + const filtered = useMemo(() => { + return documents + .filter((d) => { + const sourceMatch = + sourceFilter === "All Sources" || d.source === sourceFilter; + + const typeMatch = + typeFilter === "All Types" || d.type === typeFilter; + + const safeDate = String(d.date ?? ""); + const dateMatch = + dateFilter === "All Dates" || + safeDate.startsWith(dateFilter); + + return sourceMatch && typeMatch && dateMatch; + }) + .sort((a, b) => { + const dateA = String(a.date ?? ""); + const dateB = String(b.date ?? ""); + const titleA = String(a.title ?? ""); + const titleB = String(b.title ?? ""); + + if (sortBy === "Date (Newest)") return dateB.localeCompare(dateA); + if (sortBy === "Date (Oldest)") return dateA.localeCompare(dateB); + if (sortBy === "Title (A-Z)") return titleA.localeCompare(titleB); + if (sortBy === "Title (Z-A)") return titleB.localeCompare(titleA); + return 0; + }); + }, [documents, sourceFilter, typeFilter, dateFilter, sortBy]); + + const totalPages = Math.max( + 1, + Math.ceil(filtered.length / itemsPerPage) + ); + + const safePage = Math.min(currentPage, totalPages); + + const paginated = filtered.slice( + (safePage - 1) * itemsPerPage, + safePage * itemsPerPage + ); + + // adjust items per page based on viewport width + useEffect(() => { + const apply = () => { + const w = window.innerWidth; + if (w < 640) setItemsPerPage(3); + else if (w < 900) setItemsPerPage(6); + else setItemsPerPage(DEFAULT_ITEMS_PER_PAGE); + }; + apply(); + window.addEventListener('resize', apply); + return () => window.removeEventListener('resize', apply); + }, []); + + if (loading) { + return ( +
+

Loading documents...

+
+ ); + } + + return ( +
+
+ {/* HEADER */} +
+

Document Database

+

+ Complete collection of regulatory monitoring documents +

+
+ + {/* FILTERS */} +
+
+ + Filters: +
+ + + + + + + +
+ + +
+
+ + {/* STATS */} +
+

+ Total Documents: + + {" "} + {filtered.length} + +

+
+ + {/* TABLE */} +
+
+ Title + Ingredient + Source + Type + Date + PDF +
+ + {paginated.map((doc, index) => ( +
+
+ + {doc.title} +
+ + {doc.ingredient} + {doc.source} + {doc.type} + {doc.date} + + +
+ ))} +
+ + +
+
+ ); } \ No newline at end of file diff --git a/src/pages/IngredientSearchPage.tsx b/src/pages/IngredientSearchPage.tsx index e712231..28f9a09 100644 --- a/src/pages/IngredientSearchPage.tsx +++ b/src/pages/IngredientSearchPage.tsx @@ -1,192 +1,192 @@ -import { useEffect, useMemo, useState } from "react"; -import { Search, Filter } from "lucide-react"; -import styles from "../styles/IngredientSearchPage.module.css"; -import Pagination from "../components/Pagination"; -import { getDocuments, openPdf, type ApiDocument } from "../services/theapi"; - -export default function IngredientSearchPage() { - const [documents, setDocuments] = useState([]); - const [loading, setLoading] = useState(true); - - const [searchQuery, setSearchQuery] = useState(""); - const [sourceFilter, setSourceFilter] = useState("All Sources"); - const [typeFilter, setTypeFilter] = useState("All Types"); - const [dateFilter, setDateFilter] = useState("All Dates"); - const [currentPage, setCurrentPage] = useState(1); - const [itemsPerPage, setItemsPerPage] = useState(8); - - // FETCH API - useEffect(() => { - getDocuments() - .then((data) => setDocuments(Array.isArray(data) ? data : [])) - .catch((err) => { - console.error("API ERROR:", err); - setDocuments([]); - }) - .finally(() => setLoading(false)); - }, []); - - // responsive items per page - useEffect(() => { - const apply = () => { - const w = window.innerWidth; - if (w < 640) setItemsPerPage(3); - else if (w < 900) setItemsPerPage(6); - else setItemsPerPage(8); - }; - apply(); - window.addEventListener('resize', apply); - return () => window.removeEventListener('resize', apply); - }, []); - - // FILTER OPTIONS - const allSources = useMemo(() => [ - "All Sources", - ...new Set(documents.map((d) => d.source)), - ], [documents]); - - const allTypes = useMemo(() => [ - "All Types", - ...new Set(documents.map((d) => d.type)), - ], [documents]); - - const allDates = useMemo(() => [ - "All Dates", - ...new Set(documents.map((d) => String(d.date ?? "").slice(0, 4))).values(), - ], [documents]); - - // FILTERED RESULTS - const filtered = useMemo(() => { - return documents - .filter((d) => { - const queryMatch = - searchQuery === "" || - d.ingredient.toLowerCase().includes(searchQuery.toLowerCase()) || - d.title.toLowerCase().includes(searchQuery.toLowerCase()); - - const sourceMatch = sourceFilter === "All Sources" || d.source === sourceFilter; - const typeMatch = typeFilter === "All Types" || d.type === typeFilter; - const safeDate = String(d.date ?? ""); - const dateMatch = dateFilter === "All Dates" || safeDate.startsWith(dateFilter); - - return queryMatch && sourceMatch && typeMatch && dateMatch; - }) - .sort((a, b) => String(b.date ?? "").localeCompare(String(a.date ?? ""))); - }, [documents, searchQuery, sourceFilter, typeFilter, dateFilter]); - - if (loading) { - return

Loading...

; - } - - return ( -
-
- - {/* HEADER */} -
-

Ingredient Search

-

Search cosmetic ingredients and regulatory documents

-
- - {/* SEARCH */} -
-
- setSearchQuery(e.target.value)} - /> - -
-
- - {/* FILTERS */} -
-
- - Filters -
-
- - - -
-
- - {/* RESULTS */} -
- {filtered.length} results found -
- - {/* DOCUMENTS */} -
- {filtered.length === 0 ? ( -
-

No documents found.

-
- ) : ( - (() => { - const totalPages = Math.max(1, Math.ceil(filtered.length / itemsPerPage)); - const safePage = Math.min(currentPage, totalPages); - const paginated = filtered.slice((safePage - 1) * itemsPerPage, safePage * itemsPerPage); - return ( - <> - {paginated.map((doc) => ( -
-
-
-

{doc.title}

-
-
- Ingredient - {doc.ingredient} -
-
- Source - {doc.source} -
-
- Type - {doc.type} -
-
- Date - {doc.date ?? "—"} -
-
-
- - {doc.pdf_url && ( - - )} -
-
- ))} - - - - ); - })() - )} -
-
-
- ); +import { useEffect, useMemo, useState } from "react"; +import { Search, Filter } from "lucide-react"; +import styles from "../styles/IngredientSearchPage.module.css"; +import Pagination from "../components/Pagination"; +import { getDocuments, openPdf, type ApiDocument } from "../services/theapi"; + +export default function IngredientSearchPage() { + const [documents, setDocuments] = useState([]); + const [loading, setLoading] = useState(true); + + const [searchQuery, setSearchQuery] = useState(""); + const [sourceFilter, setSourceFilter] = useState("All Sources"); + const [typeFilter, setTypeFilter] = useState("All Types"); + const [dateFilter, setDateFilter] = useState("All Dates"); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(8); + + // FETCH API + useEffect(() => { + getDocuments() + .then((data) => setDocuments(Array.isArray(data) ? data : [])) + .catch((err) => { + console.error("API ERROR:", err); + setDocuments([]); + }) + .finally(() => setLoading(false)); + }, []); + + // responsive items per page + useEffect(() => { + const apply = () => { + const w = window.innerWidth; + if (w < 640) setItemsPerPage(3); + else if (w < 900) setItemsPerPage(6); + else setItemsPerPage(8); + }; + apply(); + window.addEventListener('resize', apply); + return () => window.removeEventListener('resize', apply); + }, []); + + // FILTER OPTIONS + const allSources = useMemo(() => [ + "All Sources", + ...new Set(documents.map((d) => d.source)), + ], [documents]); + + const allTypes = useMemo(() => [ + "All Types", + ...new Set(documents.map((d) => d.type)), + ], [documents]); + + const allDates = useMemo(() => [ + "All Dates", + ...new Set(documents.map((d) => String(d.date ?? "").slice(0, 4))).values(), + ], [documents]); + + // FILTERED RESULTS + const filtered = useMemo(() => { + return documents + .filter((d) => { + const queryMatch = + searchQuery === "" || + d.ingredient.toLowerCase().includes(searchQuery.toLowerCase()) || + d.title.toLowerCase().includes(searchQuery.toLowerCase()); + + const sourceMatch = sourceFilter === "All Sources" || d.source === sourceFilter; + const typeMatch = typeFilter === "All Types" || d.type === typeFilter; + const safeDate = String(d.date ?? ""); + const dateMatch = dateFilter === "All Dates" || safeDate.startsWith(dateFilter); + + return queryMatch && sourceMatch && typeMatch && dateMatch; + }) + .sort((a, b) => String(b.date ?? "").localeCompare(String(a.date ?? ""))); + }, [documents, searchQuery, sourceFilter, typeFilter, dateFilter]); + + if (loading) { + return

Loading...

; + } + + return ( +
+
+ + {/* HEADER */} +
+

Ingredient Search

+

Search cosmetic ingredients and regulatory documents

+
+ + {/* SEARCH */} +
+
+ setSearchQuery(e.target.value)} + /> + +
+
+ + {/* FILTERS */} +
+
+ + Filters +
+
+ + + +
+
+ + {/* RESULTS */} +
+ {filtered.length} results found +
+ + {/* DOCUMENTS */} +
+ {filtered.length === 0 ? ( +
+

No documents found.

+
+ ) : ( + (() => { + const totalPages = Math.max(1, Math.ceil(filtered.length / itemsPerPage)); + const safePage = Math.min(currentPage, totalPages); + const paginated = filtered.slice((safePage - 1) * itemsPerPage, safePage * itemsPerPage); + return ( + <> + {paginated.map((doc) => ( +
+
+
+

{doc.title}

+
+
+ Ingredient + {doc.ingredient} +
+
+ Source + {doc.source} +
+
+ Type + {doc.type} +
+
+ Date + {doc.date ?? "—"} +
+
+
+ + {doc.pdf_url && ( + + )} +
+
+ ))} + + + + ); + })() + )} +
+
+
+ ); } \ No newline at end of file diff --git a/src/pages/RecentUpdatesPage.tsx b/src/pages/RecentUpdatesPage.tsx index fc794b8..31a9246 100644 --- a/src/pages/RecentUpdatesPage.tsx +++ b/src/pages/RecentUpdatesPage.tsx @@ -1,212 +1,212 @@ -import { useEffect, useMemo, useState } from "react"; -import { TrendingUp, Filter, Eye } from "lucide-react"; -import styles from "../styles/RecentUpdatesPage.module.css"; -import Pagination from "../components/Pagination"; -import { getDocuments, openPdf, type ApiDocument } from "../services/theapi"; - -const DEFAULT_ITEMS_PER_PAGE = 8; - -export default function RecentUpdatesPage() { - const [updates, setUpdates] = useState([]); - const [loading, setLoading] = useState(true); - const [sourceFilter, setSourceFilter] = useState("All Sources"); - const [typeFilter, setTypeFilter] = useState("All Types"); - const [currentPage, setCurrentPage] = useState(1); - const [itemsPerPage, setItemsPerPage] = useState(DEFAULT_ITEMS_PER_PAGE); - - useEffect(() => { - getDocuments() - .then((data) => setUpdates(Array.isArray(data) ? data : [])) - .catch((err) => { - console.error("API ERROR:", err); - setUpdates([]); - }) - .finally(() => setLoading(false)); - }, []); - - const allSources = useMemo( - () => ["All Sources", ...new Set(updates.map((u) => u.source))], - [updates] - ); - - const allTypes = useMemo( - () => ["All Types", ...new Set(updates.map((u) => u.type))], - [updates] - ); - - const filtered = useMemo(() => { - return updates - .filter((u) => { - const sourceMatch = - sourceFilter === "All Sources" || u.source === sourceFilter; - - const typeMatch = - typeFilter === "All Types" || u.type === typeFilter; - - return sourceMatch && typeMatch; - }) - .sort((a, b) => - String(b.date ?? "").localeCompare(String(a.date ?? "")) - ); - }, [updates, sourceFilter, typeFilter]); - - const totalPages = Math.max(1, Math.ceil(filtered.length / itemsPerPage)); - const safePage = Math.min(currentPage, totalPages); - - const paginated = filtered.slice( - (safePage - 1) * itemsPerPage, - safePage * itemsPerPage - ); - - useEffect(() => { - const apply = () => { - const w = window.innerWidth; - if (w < 640) setItemsPerPage(3); - else if (w < 900) setItemsPerPage(6); - else setItemsPerPage(DEFAULT_ITEMS_PER_PAGE); - }; - apply(); - window.addEventListener('resize', apply); - return () => window.removeEventListener('resize', apply); - }, []); - - if (loading) { - return ( -
-

Loading updates...

-
- ); - } - - return ( -
-
- {/* HEADER */} -
-
- -

Recent Updates

-
-

- Latest cosmetic regulatory updates and monitoring activity -

-
- - {/* STATS */} -
-
-

Total Updates

-

{updates.length}

-
- -
-
- -
-

Sources

-

{allSources.length - 1}

-
- -
-
-
- - {/* FILTERS */} -
-
- - Filters -
- - - - -
- - {/* RESULTS */} -
- {filtered.length}{" "} - updates found -
- - {/* TABLE */} -
-
- Status - Title - Ingredient - Source - Type - Date - View -
- - {paginated.map((u, index) => ( -
-
- - New - -
- -
- {u.title} -
- -
{u.ingredient}
-
{u.source}
-
{u.type}
-
{u.date ?? "—"}
- -
- {u.pdf_url && ( - - )} -
-
- ))} -
- - -
-
- ); +import { useEffect, useMemo, useState } from "react"; +import { TrendingUp, Filter, Eye } from "lucide-react"; +import styles from "../styles/RecentUpdatesPage.module.css"; +import Pagination from "../components/Pagination"; +import { getDocuments, openPdf, type ApiDocument } from "../services/theapi"; + +const DEFAULT_ITEMS_PER_PAGE = 8; + +export default function RecentUpdatesPage() { + const [updates, setUpdates] = useState([]); + const [loading, setLoading] = useState(true); + const [sourceFilter, setSourceFilter] = useState("All Sources"); + const [typeFilter, setTypeFilter] = useState("All Types"); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(DEFAULT_ITEMS_PER_PAGE); + + useEffect(() => { + getDocuments() + .then((data) => setUpdates(Array.isArray(data) ? data : [])) + .catch((err) => { + console.error("API ERROR:", err); + setUpdates([]); + }) + .finally(() => setLoading(false)); + }, []); + + const allSources = useMemo( + () => ["All Sources", ...new Set(updates.map((u) => u.source))], + [updates] + ); + + const allTypes = useMemo( + () => ["All Types", ...new Set(updates.map((u) => u.type))], + [updates] + ); + + const filtered = useMemo(() => { + return updates + .filter((u) => { + const sourceMatch = + sourceFilter === "All Sources" || u.source === sourceFilter; + + const typeMatch = + typeFilter === "All Types" || u.type === typeFilter; + + return sourceMatch && typeMatch; + }) + .sort((a, b) => + String(b.date ?? "").localeCompare(String(a.date ?? "")) + ); + }, [updates, sourceFilter, typeFilter]); + + const totalPages = Math.max(1, Math.ceil(filtered.length / itemsPerPage)); + const safePage = Math.min(currentPage, totalPages); + + const paginated = filtered.slice( + (safePage - 1) * itemsPerPage, + safePage * itemsPerPage + ); + + useEffect(() => { + const apply = () => { + const w = window.innerWidth; + if (w < 640) setItemsPerPage(3); + else if (w < 900) setItemsPerPage(6); + else setItemsPerPage(DEFAULT_ITEMS_PER_PAGE); + }; + apply(); + window.addEventListener('resize', apply); + return () => window.removeEventListener('resize', apply); + }, []); + + if (loading) { + return ( +
+

Loading updates...

+
+ ); + } + + return ( +
+
+ {/* HEADER */} +
+
+ +

Recent Updates

+
+

+ Latest cosmetic regulatory updates and monitoring activity +

+
+ + {/* STATS */} +
+
+

Total Updates

+

{updates.length}

+
+ +
+
+ +
+

Sources

+

{allSources.length - 1}

+
+ +
+
+
+ + {/* FILTERS */} +
+
+ + Filters +
+ + + + +
+ + {/* RESULTS */} +
+ {filtered.length}{" "} + updates found +
+ + {/* TABLE */} +
+
+ Status + Title + Ingredient + Source + Type + Date + View +
+ + {paginated.map((u, index) => ( +
+
+ + New + +
+ +
+ {u.title} +
+ +
{u.ingredient}
+
{u.source}
+
{u.type}
+
{u.date ?? "—"}
+ +
+ {u.pdf_url && ( + + )} +
+
+ ))} +
+ + +
+
+ ); } \ No newline at end of file diff --git a/src/styles/DocumentsPage.module.css b/src/styles/DocumentsPage.module.css index f65aba7..0ef66fd 100644 --- a/src/styles/DocumentsPage.module.css +++ b/src/styles/DocumentsPage.module.css @@ -1,237 +1,237 @@ -.container { - padding: 2rem; -} - -.maxWidth { - max-width: 80rem; -} - -.header { - margin-bottom: 1.75rem; -} - -.title { - font-size: 2.25rem; - font-weight: 700; - color: rgb(17, 24, 39); - margin-bottom: 0.5rem; - line-height: 1.1; -} - -.description { - color: rgb(75, 85, 99); - margin-bottom: 1.5rem; - font-size: 1.125rem; - line-height: 1.6; -} - -.filtersBox { - background-color: white; - border: 1px solid rgb(229, 231, 235); - border-radius: 0.75rem; - padding: 1.25rem; - margin-bottom: 1.25rem; - display: flex; - align-items: center; - gap: 0.75rem; - flex-wrap: wrap; -} - -.filterLabel { - display: flex; - align-items: center; - gap: 0.5rem; - color: rgb(55, 65, 81); - font-weight: 500; - font-size: 0.875rem; -} - -.filterIcon { - font-size: 1rem; -} - -.select { - border: 1px solid rgb(209, 213, 219); - border-radius: 0.5rem; - font-size: 0.875rem; - padding: 0.375rem 0.75rem; - color: rgb(55, 65, 81); - background-color: white; - cursor: pointer; -} - -.select:focus { - outline: none; - box-shadow: 0 0 0 2px rgb(59, 130, 246); -} - -.sortWrapper { - margin-left: auto; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.sortIcon { - color: rgb(107, 114, 128); -} - -.statsRow { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 0.75rem; -} - -.statsText { - font-size: 0.875rem; - color: rgb(55, 65, 81); -} - -.statsNumber { - font-weight: 600; -} - -.exportButton { - display: flex; - align-items: center; - gap: 0.5rem; - border: 1px solid rgb(209, 213, 219); - border-radius: 0.5rem; - font-size: 0.875rem; - padding: 0.375rem 1rem; - color: rgb(55, 65, 81); - background-color: white; - cursor: pointer; - transition: background-color 0.2s; -} - -.exportButton:hover { - background-color: rgb(249, 250, 251); -} - -.table { - background-color: white; - border: 1px solid rgb(229, 231, 235); - border-radius: 0.75rem; - overflow: auto; - -webkit-overflow-scrolling: touch; -} - -.tableHeader { - display: grid; - grid-template-columns: minmax(180px,2fr) 1.2fr 1fr 1.2fr 0.8fr 0.6fr; - background-color: rgb(249, 250, 251); - border-bottom: 1px solid rgb(229, 231, 235); - padding: 0.75rem 1.25rem; - color: rgb(17, 24, 39); -} - -.tableRow { - display: grid; - grid-template-columns: minmax(180px,2fr) 1.2fr 1fr 1.2fr 0.8fr 0.6fr; - padding: 1rem 1.25rem; - align-items: center; - color: rgb(17, 24, 39); -} - -.tableRow span, -.titleCell span, -.titleCell { - color: rgb(17, 24, 39); -} - -.headerCell { - font-size: 0.875rem; - font-weight: 600; - color: rgb(55, 65, 81); -} - -.tableRow { - display: grid; - grid-template-columns: 2fr 1.2fr 1fr 1.2fr 0.8fr 0.6fr; - padding: 1rem 1.25rem; - align-items: center; -} - -.tableRow:hover { - background-color: rgb(249, 250, 251); - transition: background-color 0.2s; -} - -.tableRowBorder { - border-bottom: 1px solid rgb(229, 231, 235); -} - -.titleCell { - display: flex; - align-items: flex-start; - gap: 0.625rem; -} - -.titleIcon { - color: rgb(156, 163, 175); - margin-top: 0.125rem; - flex-shrink: 0; -} - -.titleText { - font-size: 0.875rem; - color: rgb(31, 41, 55); - line-height: 1.5; -} - -.ingredientBadge { - display: inline-block; - font-size: 0.75rem; - padding: 0.625rem; - border-radius: 0.375rem; - border: 1px solid rgb(209, 213, 219); - background-color: rgb(243, 244, 246); -} - -.ingredientBadgeBold { - font-weight: 600; - border-color: rgb(209, 213, 219); - color: rgb(17, 24, 39); -} - -.ingredientBadgeNormal { - border-color: rgb(229, 231, 235); - color: rgb(55, 65, 81); -} - -.cell { - font-size: 0.875rem; - color: rgb(17, 24, 39); -} - -.cellWhitespace { - white-space: nowrap; -} - -.openButton { - background-color: rgb(37, 99, 235); - color: white; - font-size: 0.875rem; - font-weight: 500; - padding: 0.375rem 1rem; - border-radius: 0.5rem; - border: none; - cursor: pointer; - transition: background-color 0.2s; -} - -.openButton:hover { - background-color: rgb(29, 78, 216); -} - - -/* Mobile: make each table row a card */ -@media (max-width: 640px) { - .tableHeader { display: none; } - .tableRow { display: block; padding: 12px; border-bottom: 1px solid rgb(229,231,235); } - .titleCell { font-weight:700; font-size:1rem; display:flex; align-items:center; gap:8px } - .tableRow span { display:block; margin-top:6px; color: rgb(17,24,39) } - .openButton { width:100%; display:block } -} +.container { + padding: 2rem; +} + +.maxWidth { + max-width: 80rem; +} + +.header { + margin-bottom: 1.75rem; +} + +.title { + font-size: 2.25rem; + font-weight: 700; + color: rgb(17, 24, 39); + margin-bottom: 0.5rem; + line-height: 1.1; +} + +.description { + color: rgb(75, 85, 99); + margin-bottom: 1.5rem; + font-size: 1.125rem; + line-height: 1.6; +} + +.filtersBox { + background-color: white; + border: 1px solid rgb(229, 231, 235); + border-radius: 0.75rem; + padding: 1.25rem; + margin-bottom: 1.25rem; + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.filterLabel { + display: flex; + align-items: center; + gap: 0.5rem; + color: rgb(55, 65, 81); + font-weight: 500; + font-size: 0.875rem; +} + +.filterIcon { + font-size: 1rem; +} + +.select { + border: 1px solid rgb(209, 213, 219); + border-radius: 0.5rem; + font-size: 0.875rem; + padding: 0.375rem 0.75rem; + color: rgb(55, 65, 81); + background-color: white; + cursor: pointer; +} + +.select:focus { + outline: none; + box-shadow: 0 0 0 2px rgb(59, 130, 246); +} + +.sortWrapper { + margin-left: auto; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.sortIcon { + color: rgb(107, 114, 128); +} + +.statsRow { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.75rem; +} + +.statsText { + font-size: 0.875rem; + color: rgb(55, 65, 81); +} + +.statsNumber { + font-weight: 600; +} + +.exportButton { + display: flex; + align-items: center; + gap: 0.5rem; + border: 1px solid rgb(209, 213, 219); + border-radius: 0.5rem; + font-size: 0.875rem; + padding: 0.375rem 1rem; + color: rgb(55, 65, 81); + background-color: white; + cursor: pointer; + transition: background-color 0.2s; +} + +.exportButton:hover { + background-color: rgb(249, 250, 251); +} + +.table { + background-color: white; + border: 1px solid rgb(229, 231, 235); + border-radius: 0.75rem; + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +.tableHeader { + display: grid; + grid-template-columns: minmax(180px,2fr) 1.2fr 1fr 1.2fr 0.8fr 0.6fr; + background-color: rgb(249, 250, 251); + border-bottom: 1px solid rgb(229, 231, 235); + padding: 0.75rem 1.25rem; + color: rgb(17, 24, 39); +} + +.tableRow { + display: grid; + grid-template-columns: minmax(180px,2fr) 1.2fr 1fr 1.2fr 0.8fr 0.6fr; + padding: 1rem 1.25rem; + align-items: center; + color: rgb(17, 24, 39); +} + +.tableRow span, +.titleCell span, +.titleCell { + color: rgb(17, 24, 39); +} + +.headerCell { + font-size: 0.875rem; + font-weight: 600; + color: rgb(55, 65, 81); +} + +.tableRow { + display: grid; + grid-template-columns: 2fr 1.2fr 1fr 1.2fr 0.8fr 0.6fr; + padding: 1rem 1.25rem; + align-items: center; +} + +.tableRow:hover { + background-color: rgb(249, 250, 251); + transition: background-color 0.2s; +} + +.tableRowBorder { + border-bottom: 1px solid rgb(229, 231, 235); +} + +.titleCell { + display: flex; + align-items: flex-start; + gap: 0.625rem; +} + +.titleIcon { + color: rgb(156, 163, 175); + margin-top: 0.125rem; + flex-shrink: 0; +} + +.titleText { + font-size: 0.875rem; + color: rgb(31, 41, 55); + line-height: 1.5; +} + +.ingredientBadge { + display: inline-block; + font-size: 0.75rem; + padding: 0.625rem; + border-radius: 0.375rem; + border: 1px solid rgb(209, 213, 219); + background-color: rgb(243, 244, 246); +} + +.ingredientBadgeBold { + font-weight: 600; + border-color: rgb(209, 213, 219); + color: rgb(17, 24, 39); +} + +.ingredientBadgeNormal { + border-color: rgb(229, 231, 235); + color: rgb(55, 65, 81); +} + +.cell { + font-size: 0.875rem; + color: rgb(17, 24, 39); +} + +.cellWhitespace { + white-space: nowrap; +} + +.openButton { + background-color: rgb(37, 99, 235); + color: white; + font-size: 0.875rem; + font-weight: 500; + padding: 0.375rem 1rem; + border-radius: 0.5rem; + border: none; + cursor: pointer; + transition: background-color 0.2s; +} + +.openButton:hover { + background-color: rgb(29, 78, 216); +} + + +/* Mobile: make each table row a card */ +@media (max-width: 640px) { + .tableHeader { display: none; } + .tableRow { display: block; padding: 12px; border-bottom: 1px solid rgb(229,231,235); } + .titleCell { font-weight:700; font-size:1rem; display:flex; align-items:center; gap:8px } + .tableRow span { display:block; margin-top:6px; color: rgb(17,24,39) } + .openButton { width:100%; display:block } +} diff --git a/src/styles/RecentUpdatesPage.module.css b/src/styles/RecentUpdatesPage.module.css index 701e5a8..40617d4 100644 --- a/src/styles/RecentUpdatesPage.module.css +++ b/src/styles/RecentUpdatesPage.module.css @@ -1,278 +1,278 @@ -.container { - padding: 2rem; -} - -.maxWidth { - max-width: 96rem; -} - -.header { - margin-bottom: 2rem; -} - -.headerFlex { - display: flex; - align-items: center; - gap: 0.75rem; - margin-bottom: 0.25rem; -} - -.headerIcon { - color: rgb(37, 99, 235); - font-size: 1.75rem; -} - -.title { - font-size: 2.25rem; - font-weight: 700; - color: rgb(17, 24, 39); - line-height: 1.1; -} - -.subtitle { - color: rgb(75, 85, 99); - margin-bottom: 2rem; - font-size: 1.125rem; - line-height: 1.6; -} - -.statsGrid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 1rem; - margin-bottom: 2rem; -} - -.statCard { - background-color: white; - border: 1px solid rgb(229, 231, 235); - border-radius: 0.75rem; - padding: 1.25rem; -} - -.statLabel { - color: rgb(75, 85, 99); - font-size: 0.875rem; - font-weight: 500; - margin-bottom: 0.5rem; -} - -.statValue { - font-size: 1.875rem; - font-weight: bold; - color: rgb(17, 24, 39); -} - -.statIcon { - margin-top: 0.75rem; - width: 2rem; - height: 2rem; - border-radius: 0.5rem; - display: flex; - align-items: center; - justify-content: center; -} - -.statIconBlue { - background-color: rgb(219, 234, 254); - color: rgb(37, 99, 235); -} - -.statIconGreen { - background-color: rgb(220, 252, 231); - color: rgb(34, 197, 94); -} - -.filtersBox { - background-color: white; - border: 1px solid rgb(229, 231, 235); - border-radius: 0.75rem; - padding: 1.5rem; - margin-bottom: 1.5rem; - display: flex; - align-items: center; - gap: 0.75rem; - flex-wrap: wrap; -} - -.filterLabel { - display: flex; - align-items: center; - gap: 0.5rem; - color: rgb(55, 65, 81); - font-weight: 500; - font-size: 0.875rem; -} - -.filterIcon { - font-size: 1rem; -} - -.select { - border: 1px solid rgb(209, 213, 219); - border-radius: 0.5rem; - font-size: 0.875rem; - padding: 0.375rem 0.75rem; - color: rgb(55, 65, 81); - background-color: white; - cursor: pointer; -} - -.select:focus { - outline: none; - box-shadow: 0 0 0 2px rgb(59, 130, 246); -} - -.exportWrapper { - margin-left: auto; -} - -.exportButton { - display: flex; - align-items: center; - gap: 0.5rem; - border: 1px solid rgb(209, 213, 219); - border-radius: 0.5rem; - font-size: 0.875rem; - padding: 0.375rem 1rem; - color: rgb(55, 65, 81); - background-color: white; - cursor: pointer; - transition: background-color 0.2s; -} - -.exportButton:hover { - background-color: rgb(249, 250, 251); -} - -.resultsInfo { - color: rgb(75, 85, 99); - margin-bottom: 1rem; - font-size: 0.875rem; -} - -.resultsCount { - font-weight: 600; -} - -.table { - background-color: white; - border: 1px solid rgb(229, 231, 235); - border-radius: 0.75rem; - overflow: auto; - -webkit-overflow-scrolling: touch; -} - -.tableHeader { - display: grid; - grid-template-columns: 0.8fr minmax(180px,2fr) 1fr 1.2fr 1fr 0.8fr 0.6fr; - gap: 1rem; - background-color: rgb(249, 250, 251); - border-bottom: 1px solid rgb(229, 231, 235); - padding: 0.75rem 1.5rem; -} - -.headerCell { - font-size: 0.875rem; - font-weight: 600; - color: rgb(55, 65, 81); -} - -.tableRow { - display: grid; - grid-template-columns: 0.8fr minmax(180px,2fr) 1fr 1.2fr 1fr 0.8fr 0.6fr; - gap: 1rem; - padding: 1rem 1.5rem; - align-items: center; -} - -.tableRow:hover { - background-color: rgb(249, 250, 251); - transition: background-color 0.2s; -} - -.tableRowBorder { - border-bottom: 1px solid rgb(229, 231, 235); -} - -.statusBadge { - display: inline-block; - padding: 0.25rem 0.75rem; - border-radius: 9999px; - font-size: 0.75rem; - font-weight: 600; -} - -.statusBadgeNew { - background-color: rgb(220, 252, 231); - color: rgb(22, 163, 74); -} - -.statusBadgeUpdated { - background-color: rgb(219, 234, 254); - color: rgb(37, 99, 235); -} - -.titleCell { - font-weight: 500; - color: rgb(17, 24, 39); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.cell { - font-size: 0.875rem; - color: rgb(75, 85, 99); -} - -.cellTruncate { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.viewButton { - background-color: rgb(37, 99, 235); - color: white; - font-size: 0.75rem; - font-weight: 500; - padding: 0.375rem 0.75rem; - border-radius: 0.375rem; - border: none; - cursor: pointer; - transition: background-color 0.2s; -} - -.viewButton:hover { - background-color: rgb(29, 78, 216); -} - -@media (max-width: 1024px) { - .statsGrid { - grid-template-columns: repeat(2, 1fr); - } - - .tableHeader, - .tableRow { - grid-template-columns: 0.6fr 1.5fr 0.8fr 1fr 0.8fr 0.6fr 0.5fr; - } -} - -@media (max-width: 768px) { - .statsGrid { - grid-template-columns: 1fr; - } - - .tableHeader, - .tableRow { - grid-template-columns: 0.6fr 1.2fr 0.6fr 0.8fr 0.6fr 0.5fr 0.4fr; - gap: 0.5rem; - padding: 0.75rem 1rem; - } - - .headerCell, - .cell { - font-size: 0.75rem; - } -} +.container { + padding: 2rem; +} + +.maxWidth { + max-width: 96rem; +} + +.header { + margin-bottom: 2rem; +} + +.headerFlex { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.25rem; +} + +.headerIcon { + color: rgb(37, 99, 235); + font-size: 1.75rem; +} + +.title { + font-size: 2.25rem; + font-weight: 700; + color: rgb(17, 24, 39); + line-height: 1.1; +} + +.subtitle { + color: rgb(75, 85, 99); + margin-bottom: 2rem; + font-size: 1.125rem; + line-height: 1.6; +} + +.statsGrid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1rem; + margin-bottom: 2rem; +} + +.statCard { + background-color: white; + border: 1px solid rgb(229, 231, 235); + border-radius: 0.75rem; + padding: 1.25rem; +} + +.statLabel { + color: rgb(75, 85, 99); + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.statValue { + font-size: 1.875rem; + font-weight: bold; + color: rgb(17, 24, 39); +} + +.statIcon { + margin-top: 0.75rem; + width: 2rem; + height: 2rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.statIconBlue { + background-color: rgb(219, 234, 254); + color: rgb(37, 99, 235); +} + +.statIconGreen { + background-color: rgb(220, 252, 231); + color: rgb(34, 197, 94); +} + +.filtersBox { + background-color: white; + border: 1px solid rgb(229, 231, 235); + border-radius: 0.75rem; + padding: 1.5rem; + margin-bottom: 1.5rem; + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.filterLabel { + display: flex; + align-items: center; + gap: 0.5rem; + color: rgb(55, 65, 81); + font-weight: 500; + font-size: 0.875rem; +} + +.filterIcon { + font-size: 1rem; +} + +.select { + border: 1px solid rgb(209, 213, 219); + border-radius: 0.5rem; + font-size: 0.875rem; + padding: 0.375rem 0.75rem; + color: rgb(55, 65, 81); + background-color: white; + cursor: pointer; +} + +.select:focus { + outline: none; + box-shadow: 0 0 0 2px rgb(59, 130, 246); +} + +.exportWrapper { + margin-left: auto; +} + +.exportButton { + display: flex; + align-items: center; + gap: 0.5rem; + border: 1px solid rgb(209, 213, 219); + border-radius: 0.5rem; + font-size: 0.875rem; + padding: 0.375rem 1rem; + color: rgb(55, 65, 81); + background-color: white; + cursor: pointer; + transition: background-color 0.2s; +} + +.exportButton:hover { + background-color: rgb(249, 250, 251); +} + +.resultsInfo { + color: rgb(75, 85, 99); + margin-bottom: 1rem; + font-size: 0.875rem; +} + +.resultsCount { + font-weight: 600; +} + +.table { + background-color: white; + border: 1px solid rgb(229, 231, 235); + border-radius: 0.75rem; + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +.tableHeader { + display: grid; + grid-template-columns: 0.8fr minmax(180px,2fr) 1fr 1.2fr 1fr 0.8fr 0.6fr; + gap: 1rem; + background-color: rgb(249, 250, 251); + border-bottom: 1px solid rgb(229, 231, 235); + padding: 0.75rem 1.5rem; +} + +.headerCell { + font-size: 0.875rem; + font-weight: 600; + color: rgb(55, 65, 81); +} + +.tableRow { + display: grid; + grid-template-columns: 0.8fr minmax(180px,2fr) 1fr 1.2fr 1fr 0.8fr 0.6fr; + gap: 1rem; + padding: 1rem 1.5rem; + align-items: center; +} + +.tableRow:hover { + background-color: rgb(249, 250, 251); + transition: background-color 0.2s; +} + +.tableRowBorder { + border-bottom: 1px solid rgb(229, 231, 235); +} + +.statusBadge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 600; +} + +.statusBadgeNew { + background-color: rgb(220, 252, 231); + color: rgb(22, 163, 74); +} + +.statusBadgeUpdated { + background-color: rgb(219, 234, 254); + color: rgb(37, 99, 235); +} + +.titleCell { + font-weight: 500; + color: rgb(17, 24, 39); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.cell { + font-size: 0.875rem; + color: rgb(75, 85, 99); +} + +.cellTruncate { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.viewButton { + background-color: rgb(37, 99, 235); + color: white; + font-size: 0.75rem; + font-weight: 500; + padding: 0.375rem 0.75rem; + border-radius: 0.375rem; + border: none; + cursor: pointer; + transition: background-color 0.2s; +} + +.viewButton:hover { + background-color: rgb(29, 78, 216); +} + +@media (max-width: 1024px) { + .statsGrid { + grid-template-columns: repeat(2, 1fr); + } + + .tableHeader, + .tableRow { + grid-template-columns: 0.6fr 1.5fr 0.8fr 1fr 0.8fr 0.6fr 0.5fr; + } +} + +@media (max-width: 768px) { + .statsGrid { + grid-template-columns: 1fr; + } + + .tableHeader, + .tableRow { + grid-template-columns: 0.6fr 1.2fr 0.6fr 0.8fr 0.6fr 0.5fr 0.4fr; + gap: 0.5rem; + padding: 0.75rem 1rem; + } + + .headerCell, + .cell { + font-size: 0.75rem; + } +}