diff --git a/src/apps/content-editor/src/app/views/ItemList/ConfirmPublishesDialog.tsx b/src/apps/content-editor/src/app/views/ItemList/ConfirmPublishesDialog.tsx index c45e9dd79e..3516f395f7 100644 --- a/src/apps/content-editor/src/app/views/ItemList/ConfirmPublishesDialog.tsx +++ b/src/apps/content-editor/src/app/views/ItemList/ConfirmPublishesDialog.tsx @@ -49,7 +49,8 @@ export const ConfirmPublishesModal = ({ - Publish {items.length} {pluralizeWord("Item", items.length)}: + Publish Changes to {items.length}{" "} + {pluralizeWord("Item", items.length)}: This will make the the following {items.length} content{" "} @@ -76,7 +77,7 @@ export const ConfirmPublishesModal = ({ }} data-cy="ConfirmPublishButton" > - Publish Changes to ({items.length}) Items + Publish Items ({items.length}) diff --git a/src/apps/content-editor/src/app/views/ItemList/ItemListFilters.tsx b/src/apps/content-editor/src/app/views/ItemList/ItemListFilters.tsx index ab68f247b2..b791dae062 100644 --- a/src/apps/content-editor/src/app/views/ItemList/ItemListFilters.tsx +++ b/src/apps/content-editor/src/app/views/ItemList/ItemListFilters.tsx @@ -1,4 +1,4 @@ -import { Box, Menu, MenuItem, Button } from "@mui/material"; +import { Box, Menu, MenuItem, Button, Typography } from "@mui/material"; import { DateFilter, FilterButton, @@ -7,9 +7,13 @@ import { import { useEffect, useMemo, useState } from "react"; import { useParams } from "../../../../../../shell/hooks/useParams"; import { ArrowDropDownOutlined } from "@mui/icons-material"; -import { useGetLangsQuery } from "../../../../../../shell/services/instance"; +import { + useGetContentModelFieldsQuery, + useGetLangsQuery, +} from "../../../../../../shell/services/instance"; import { useDateFilterParams } from "../../../../../../shell/hooks/useDateFilterParams"; import { useGetUsersQuery } from "../../../../../../shell/services/accounts"; +import { useParams as useRouterParams } from "react-router"; const SORT_ORDER = { dateSaved: "Date Saved", @@ -24,6 +28,29 @@ const STATUS_FILTER = { notPublished: "Not Published", } as const; +const FILTERABLE_DATA_TYPES = [ + "text", + "wysiwyg_basic", + "wysiwyg_advanced", + "article_writer", + "markdown", + "textarea", + "number", + "images", + "date", + "datetime", + "one_to_many", + "one_to_one", + "uuid", + "number", + "currency", + "date", + "datetime", + "link", + "internal_link", + "sort", +] as const; + const getCountryCode = (langCode: string) => { if (!langCode) return ""; const splitTag = langCode.split("-"); @@ -48,6 +75,7 @@ const getFlagEmojiFromIETFTag = (langCode: string) => { }; export const ItemListFilters = () => { + const { modelZUID } = useRouterParams<{ modelZUID: string }>(); const [anchorEl, setAnchorEl] = useState({ currentTarget: null, id: "", @@ -57,6 +85,8 @@ export const ItemListFilters = () => { const { data: languages } = useGetLangsQuery({}); const activeLanguageCode = params.get("lang"); const { data: users } = useGetUsersQuery(); + const { data: fields, isFetching: isFieldsFetching } = + useGetContentModelFieldsQuery(modelZUID); const userOptions = useMemo(() => { return users?.map((user) => ({ @@ -73,7 +103,9 @@ export const ItemListFilters = () => { filterId="sortByFilter" isFilterActive={false} buttonText={`Sort: ${ - SORT_ORDER[params.get("sort") as keyof typeof SORT_ORDER] ?? + (SORT_ORDER[params.get("sort") as keyof typeof SORT_ORDER] || + fields?.find((field) => field.name === params.get("sort")) + ?.label) ?? SORT_ORDER.dateSaved }`} onOpenMenu={(event: React.MouseEvent) => { @@ -92,6 +124,13 @@ export const ItemListFilters = () => { vertical: -8, horizontal: "left", }} + // add set width to the menu + PaperProps={{ + sx: { + width: "240px", + maxHeight: "420px", + }, + }} > {Object.entries(SORT_ORDER).map(([key, value]) => ( { {value} ))} + `1px solid ${theme.palette.border}`, + }} + > + FIELDS + + {fields + ?.filter((field) => + FILTERABLE_DATA_TYPES.includes(field.datatype as any) + ) + ?.map((field) => ( + { + setParams(field.name, "sort"); + setAnchorEl({ + currentTarget: null, + id: "", + }); + }} + selected={params.get("sort") === field.name} + > + + {field.label} + + + ))} - + ); } diff --git a/src/apps/content-editor/src/app/views/ItemList/SchedulePublishesDialog.tsx b/src/apps/content-editor/src/app/views/ItemList/SchedulePublishesDialog.tsx index ecf7d6196f..5967d719c2 100644 --- a/src/apps/content-editor/src/app/views/ItemList/SchedulePublishesDialog.tsx +++ b/src/apps/content-editor/src/app/views/ItemList/SchedulePublishesDialog.tsx @@ -74,7 +74,7 @@ export const SchedulePublishesModal = ({ - You can always cancel the publish later if needed + You can always cancel the scheduled publish later if needed diff --git a/src/apps/content-editor/src/app/views/ItemList/index.tsx b/src/apps/content-editor/src/app/views/ItemList/index.tsx index 4928f4dcda..671ce698af 100644 --- a/src/apps/content-editor/src/app/views/ItemList/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemList/index.tsx @@ -127,7 +127,8 @@ export const ItemList = () => { clonedItem.publishing = publishings?.find( (publishing) => publishing.itemZUID === item.meta.ZUID && - publishing.version === item.meta.version + publishing.version === item.meta.version && + publishing.unpublishAt === null ); if (clonedItem.publishing) { clonedItem.publishing = { @@ -140,7 +141,8 @@ export const ItemList = () => { clonedItem.priorPublishing = publishings?.find( (publishing) => publishing.itemZUID === item.meta.ZUID && - publishing.version !== item.meta.version + publishing.version !== item.meta.version && + publishing.unpublishAt === null ); if (clonedItem.priorPublishing) { clonedItem.priorPublishing = { @@ -192,7 +194,7 @@ export const ItemList = () => { if (!clonedItem.data[key]) return; clonedItem.data[key] = new Date( - clonedItem.data[key] + clonedItem.data[key] + "T00:00:00" )?.toLocaleDateString("en-US", { year: "numeric", month: "short", @@ -215,73 +217,98 @@ export const ItemList = () => { const sortedAndFilteredItems = useMemo(() => { let clonedItems = [...processedItems]; clonedItems?.sort((a: any, b: any) => { - if (sort === "datePublished") { - // Handle undefined publishAt by setting a default far-future date for sorting purposes + if (sort) { + if (sort === "datePublished") { + // Handle undefined publishAt by setting a default far-future date for sorting purposes - let dateA = a?.publishing?.publishAt || a?.priorPublishing?.publishAt; - dateA = dateA ? new Date(dateA).getTime() : Number.NEGATIVE_INFINITY; + let dateA = a?.publishing?.publishAt || a?.priorPublishing?.publishAt; + dateA = dateA ? new Date(dateA).getTime() : Number.NEGATIVE_INFINITY; - let dateB = b?.publishing?.publishAt || b?.priorPublishing?.publishAt; - dateB = dateB ? new Date(dateB).getTime() : Number.NEGATIVE_INFINITY; + let dateB = b?.publishing?.publishAt || b?.priorPublishing?.publishAt; + dateB = dateB ? new Date(dateB).getTime() : Number.NEGATIVE_INFINITY; - return dateB - dateA; - } else if (sort === "dateCreated") { - return ( - new Date(b.meta.createdAt).getTime() - - new Date(a.meta.createdAt).getTime() - ); - } else if (sort === "status") { - const aPublishing = a?.publishing || a?.priorPublishing; - const bPublishing = b?.publishing || b?.priorPublishing; - - const aDate = aPublishing?.publishAt - ? new Date(aPublishing.publishAt) - : null; - const bDate = bPublishing?.publishAt - ? new Date(bPublishing.publishAt) - : null; - - // Determine the status of each item - const aIsPublished = aDate && aDate <= now; - const bIsPublished = bDate && bDate <= now; - - const aIsScheduled = aDate && aDate > now; - const bIsScheduled = bDate && bDate > now; - - // Check if meta.version exists - const aHasVersion = a?.meta?.version != null; - const bHasVersion = b?.meta?.version != null; - - // Place items without meta.version at the bottom - if (!aHasVersion && bHasVersion) { - return 1; - } else if (aHasVersion && !bHasVersion) { - return -1; - } - - // Continue with the original sorting logic - if (aIsPublished && !bIsPublished) { - return -1; // A is published, B is not - } else if (!aIsPublished && bIsPublished) { - return 1; // B is published, A is not - } else if (aIsPublished && bIsPublished) { - return bDate.getTime() - aDate.getTime(); // Both are published, sort by publish date descending - } - - if (aIsScheduled && !bIsScheduled) { - return -1; // A is scheduled, B is not - } else if (!aIsScheduled && bIsScheduled) { - return 1; // B is scheduled, A is not - } else if (aIsScheduled && bIsScheduled) { - return aDate.getTime() - bDate.getTime(); // Both are scheduled, sort by publish date ascending + return dateB - dateA; + } else if (sort === "dateCreated") { + return ( + new Date(b.meta.createdAt).getTime() - + new Date(a.meta.createdAt).getTime() + ); + } else if (sort === "status") { + const aPublishing = a?.publishing || a?.priorPublishing; + const bPublishing = b?.publishing || b?.priorPublishing; + + const aDate = aPublishing?.publishAt + ? new Date(aPublishing.publishAt) + : null; + const bDate = bPublishing?.publishAt + ? new Date(bPublishing.publishAt) + : null; + + // Determine the status of each item + const aIsPublished = aDate && aDate <= now; + const bIsPublished = bDate && bDate <= now; + + const aIsScheduled = aDate && aDate > now; + const bIsScheduled = bDate && bDate > now; + + // Check if meta.version exists + const aHasVersion = a?.meta?.version != null; + const bHasVersion = b?.meta?.version != null; + + // Place items without meta.version at the bottom + if (!aHasVersion && bHasVersion) { + return 1; + } else if (aHasVersion && !bHasVersion) { + return -1; + } + + // Continue with the original sorting logic + if (aIsPublished && !bIsPublished) { + return -1; // A is published, B is not + } else if (!aIsPublished && bIsPublished) { + return 1; // B is published, A is not + } else if (aIsPublished && bIsPublished) { + return bDate.getTime() - aDate.getTime(); // Both are published, sort by publish date descending + } + + if (aIsScheduled && !bIsScheduled) { + return -1; // A is scheduled, B is not + } else if (!aIsScheduled && bIsScheduled) { + return 1; // B is scheduled, A is not + } else if (aIsScheduled && bIsScheduled) { + return aDate.getTime() - bDate.getTime(); // Both are scheduled, sort by publish date ascending + } + + return 0; // Neither is published nor scheduled, consider them equal + } else if (fields?.find((field) => field.name === sort)) { + const dataType = fields?.find( + (field) => field.name === sort + )?.datatype; + if (typeof a.data[sort] === "number") { + if (a.data[sort] == null) return 1; + if (b.data[sort] == null) return -1; + + return dataType === "sort" + ? a.data[sort] - b.data[sort] + : b.data[sort] - a.data[sort]; + } + if (dataType === "date" || dataType === "datetime") { + return ( + new Date(b.data[sort]).getTime() - + new Date(a.data[sort]).getTime() + ); + } + const aValue = + dataType === "images" ? a.data[sort]?.filename : a.data[sort]; + const bValue = + dataType === "images" ? b.data[sort]?.filename : b.data[sort]; + return aValue?.trim()?.localeCompare(bValue?.trim()); + } else { + return ( + new Date(b.meta.updatedAt).getTime() - + new Date(a.meta.updatedAt).getTime() + ); } - - return 0; // Neither is published nor scheduled, consider them equal - } else { - return ( - new Date(b.meta.updatedAt).getTime() - - new Date(a.meta.updatedAt).getTime() - ); } }); if (search) { diff --git a/src/shell/components/FieldTypeDateTime/index.tsx b/src/shell/components/FieldTypeDateTime/index.tsx index 292248a97d..abb8159795 100644 --- a/src/shell/components/FieldTypeDateTime/index.tsx +++ b/src/shell/components/FieldTypeDateTime/index.tsx @@ -299,6 +299,11 @@ export const FieldTypeDateTime = ({ value={timezoneOptionsWithSuggestions.find( (tz) => tz.id === timezone )} + sx={{ + "& .MuiAutocomplete-inputRoot": { + py: 0.75, + }, + }} renderInput={(params) => } renderOption={(props, option) => ( = ({ sx={{ backgroundColor: "common.white", height: "28px", + maxWidth: "320px", }} > - {buttonText} + + {buttonText} + {children} diff --git a/src/shell/services/types.ts b/src/shell/services/types.ts index c654351e41..62c7cd1aac 100644 --- a/src/shell/services/types.ts +++ b/src/shell/services/types.ts @@ -232,7 +232,8 @@ export type ContentModelFieldDataType = | "link" | "internal_link" | "yes_no" - | "color"; + | "color" + | "sort"; export interface ContentModelField { ZUID: string;