diff --git a/adminSiteClient/DataInsightIndexPage.tsx b/adminSiteClient/DataInsightIndexPage.tsx
index c53d46a418..6dd7944a37 100644
--- a/adminSiteClient/DataInsightIndexPage.tsx
+++ b/adminSiteClient/DataInsightIndexPage.tsx
@@ -1,5 +1,12 @@
-import { useCallback, useContext, useEffect, useMemo, useState } from "react"
-import * as React from "react"
+import {
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+ useCallback,
+ createContext,
+ Fragment,
+} from "react"
import {
Button,
Card,
@@ -35,10 +42,11 @@ import { Admin } from "./Admin.js"
import {
ALL_GRAPHER_CHART_TYPES,
DbEnrichedImageWithUserId,
- DbPlainTag,
GRAPHER_MAP_TYPE,
GrapherChartOrMapType,
OwidGdocDataInsightIndexItem,
+ MinimalTag,
+ MinimalTagWithIsTopic,
} from "@ourworldindata/types"
import {
copyToClipboard,
@@ -58,6 +66,7 @@ import {
} from "./imagesHelpers.js"
import { ReuploadImageForDataInsightModal } from "./ReuploadImageForDataInsightModal.js"
import { CreateDataInsightModal } from "./CreateDataInsightModal.js"
+import { EditableTags } from "./EditableTags.js"
type NarrativeDataInsightIndexItem = RequiredBy<
OwidGdocDataInsightIndexItem,
@@ -72,12 +81,10 @@ type DataInsightIndexItemThatCanBeUploaded =
| NarrativeDataInsightIndexItem
| FigmaDataInsightIndexItem
-type ChartTypeFilter = GrapherChartOrMapType | "all"
-type PublicationFilter = "all" | "published" | "scheduled" | "draft"
+type ChartTypeFilter = GrapherChartOrMapType
+type PublicationFilter = "published" | "scheduled" | "draft"
type Layout = "list" | "gallery"
-const DEFAULT_CHART_TYPE_FILTER: ChartTypeFilter = "all"
-const DEFAULT_PUBLICATION_FILTER: PublicationFilter = "all"
const DEFAULT_LAYOUT: Layout = "list"
const editIcon =
@@ -88,9 +95,11 @@ const copyIcon =
const panoramaIcon =
const plusIcon =
-const NotificationContext = React.createContext(null)
+const NotificationContext = createContext(null)
function createColumns(ctx: {
+ availableTopicTags: MinimalTag[]
+ updateTags: (gdocId: string, tags: MinimalTag[]) => Promise
highlightFn: (
text: string | null | undefined
) => React.ReactElement | string
@@ -102,6 +111,7 @@ function createColumns(ctx: {
{
title: "Preview",
key: "preview",
+ width: 200,
render: (_, dataInsight) =>
hasImage(dataInsight) ? (
<>
@@ -152,10 +162,10 @@ function createColumns(ctx: {
render: (authors: string[], dataInsight) => (
<>
{authors.map((author, index) => (
-
+
{ctx.highlightFn(author)}
{index < authors.length - 1 ? ", " : ""}
-
+
))}
{dataInsight.approvedBy &&
` (approved by ${dataInsight.approvedBy})`}
@@ -166,18 +176,15 @@ function createColumns(ctx: {
title: "Topic tags",
dataIndex: "tags",
key: "tags",
- render: (tags: DbPlainTag[]) =>
- tags.map((tag) => (
-
- {ctx.highlightFn(tag.name)}
-
- )),
+ render: (tags, dataInsight) => (
+
+ ctx.updateTags(dataInsight.id, tags as MinimalTag[])
+ }
+ suggestions={ctx.availableTopicTags}
+ />
+ ),
},
{
title: "Published",
@@ -289,12 +296,18 @@ export function DataInsightIndexPage() {
const [dataInsights, setDataInsights, refreshDataInsights] =
useDataInsights(admin)
+ const [availableTopicTags, setAvailableTopicTags] = useState(
+ []
+ )
+
const [searchValue, setSearchValue] = useState("")
+ const [topicTagFilter, setTopicTagFilter] = useState()
const [chartTypeFilter, setChartTypeFilter] = useState<
- GrapherChartOrMapType | "all"
- >(DEFAULT_CHART_TYPE_FILTER)
- const [publicationFilter, setPublicationFilter] =
- useState(DEFAULT_PUBLICATION_FILTER)
+ GrapherChartOrMapType | undefined
+ >()
+ const [publicationFilter, setPublicationFilter] = useState<
+ PublicationFilter | undefined
+ >()
const [layout, setLayout] = useState(DEFAULT_LAYOUT)
const [dataInsightForImageUpload, setDataInsightForImageUpload] =
@@ -311,16 +324,24 @@ export function DataInsightIndexPage() {
)
const filteredDataInsights = useMemo(() => {
+ const topicTagFilterFn = (
+ dataInsight: OwidGdocDataInsightIndexItem
+ ) => {
+ if (!topicTagFilter) return true
+ return dataInsight.tags?.some((tag) => tag.name === topicTagFilter)
+ }
+
const chartTypeFilterFn = (
dataInsight: OwidGdocDataInsightIndexItem
) => {
- if (chartTypeFilter === "all") return true
+ if (!chartTypeFilter) return true
return dataInsight.chartType === chartTypeFilter
}
const publicationFilterFn = (
dataInsight: OwidGdocDataInsightIndexItem
) => {
+ if (!publicationFilter) return true
switch (publicationFilter) {
case "draft":
return !dataInsight.published
@@ -334,8 +355,6 @@ export function DataInsightIndexPage() {
dataInsight.published &&
dayjs(dataInsight.publishedAt).isBefore(dayjs())
)
- case "all":
- return true
}
}
@@ -353,11 +372,35 @@ export function DataInsightIndexPage() {
return dataInsights.filter(
(di) =>
+ topicTagFilterFn(di) &&
chartTypeFilterFn(di) &&
publicationFilterFn(di) &&
searchFilterFn(di)
)
- }, [dataInsights, chartTypeFilter, publicationFilter, searchWords])
+ }, [
+ dataInsights,
+ topicTagFilter,
+ chartTypeFilter,
+ publicationFilter,
+ searchWords,
+ ])
+
+ const updateTags = useCallback(
+ async (gdocId: string, tags: MinimalTag[]) => {
+ const json = await admin.requestJSON(
+ `/api/gdocs/${gdocId}/setTags`,
+ { tagIds: tags.map((t) => t.id) },
+ "POST"
+ )
+ if (json.success) {
+ const dataInsight = dataInsights.find(
+ (gdoc) => gdoc.id === gdocId
+ )
+ if (dataInsight) dataInsight.tags = tags
+ }
+ },
+ [admin, dataInsights]
+ )
const columns = useMemo(() => {
const highlightFn = highlightFunctionForSearchWords(searchWords)
@@ -367,10 +410,12 @@ export function DataInsightIndexPage() {
) => setDataInsightForImageUpload(dataInsight)
return createColumns({
+ availableTopicTags,
+ updateTags,
highlightFn,
triggerImageUploadFlow,
})
- }, [searchWords])
+ }, [searchWords, availableTopicTags, updateTags])
const updateDataInsightPreview = (
dataInsightId: string,
@@ -417,13 +462,26 @@ export function DataInsightIndexPage() {
}
}
+ useEffect(() => {
+ const fetchTags = () =>
+ admin.getJSON<{ tags: MinimalTagWithIsTopic[] }>("/api/tags.json")
+
+ void fetchTags().then((result) =>
+ setAvailableTopicTags(result.tags.filter((tag) => tag.isTopic))
+ )
+ }, [admin])
+
return (
{notificationContextHolder}
-
-
+
+
{
if (e.key === "Escape") setSearchValue("")
}}
- style={{ width: 500, marginBottom: 20 }}
+ style={{ width: 350 }}
+ />
+