@vonaffenfels/contentful-teasermanager 1.1.68 → 1.1.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,179 +1,189 @@
1
- import {EntityList, Pagination, Spinner, Select, Autocomplete, TextInput} from "@contentful/f36-components";
2
- import {getContentfulClient} from "../../../lib/contentfulClient";
3
- import {useEffect, useState} from "react";
4
- import format from "date-fns/format"
5
- import useDebounce from "../../../hooks/useDebounce";
6
- import classNames from "classnames";
7
-
8
- export const NewestArticles = ({
9
- slotState = {},
10
- portals = {},
11
- sdk,
12
- onEntryClick = () => alert("onEntryClick missing"),
13
- getArticleThumbnailUrl = (article) => alert("getArticleThumbnailUrl missing"),
14
- }) => {
15
- const {portal, slotId} = sdk.parameters.invocation;
16
- const contentfulClient = getContentfulClient();
17
- const limit = 25;
18
- const [searchQuery, setSearchQuery] = useState("");
19
- const [authorSearchQuery, setAuthorSearchQuery] = useState("");
20
- const [enabledPortals, setEnabledPortals] = useState([portal]);
21
- const [selectedPortal, setSelectedPortal] = useState(portal);
22
- const [loading, setLoading] = useState(true);
23
- const [data, setData] = useState({items: [], total: 0});
24
- const [page, setPage] = useState(0);
25
- const [selectedAuthor, setSelectedAuthor] = useState(null);
26
- const [authors, setAuthors] = useState([]);
27
- const [authorsLoading, setAuthorsLoading] = useState(false);
28
-
29
- const debouncedSearch = useDebounce(searchQuery, 500);
30
- const debouncedAuthorSearch = useDebounce(authorSearchQuery, 500);
31
-
32
- useEffect(() => {
33
- sdk.space.getContentType("article").then(articleType => {
34
- let enabledPortals = articleType.fields.find(v => v.id === "portal")?.validations?.find(v => !!v.in)?.in;
35
-
36
- if (enabledPortals) {
37
- setEnabledPortals(enabledPortals);
38
- }
39
- });
40
- }, []);
41
-
42
- useEffect(() => {
43
- setPage(0);
44
- }, [searchQuery, selectedAuthor, selectedPortal]);
45
-
46
- useEffect(() => {
47
- setLoading(true);
48
- const params = {
49
- limit: limit,
50
- skip: page * limit,
51
- content_type: "article",
52
- order: "-fields.teaserDate",
53
- "fields.portal": selectedPortal,
54
- };
55
-
56
- if (searchQuery) {
57
- params["fields.title[match]"] = searchQuery;
58
- }
59
-
60
- if (selectedAuthor) {
61
- params["fields.authors.sys.id"] = selectedAuthor;
62
- }
63
-
64
- contentfulClient.getEntries(params).then((response) => {
65
- setLoading(false);
66
- setData(response);
67
- });
68
- }, [debouncedSearch, selectedAuthor,selectedPortal, limit, page]);
69
-
70
- useEffect(() => {
71
- setAuthorsLoading(true);
72
- const params = {
73
- limit: 100,
74
- content_type: "author",
75
- order: "fields.name",
76
- "fields.portal": portal,
77
- };
78
-
79
- if (debouncedAuthorSearch) {
80
- params["fields.name[match]"] = debouncedAuthorSearch
81
- } else {
82
- setSelectedAuthor(null);
83
- }
84
-
85
- contentfulClient.getEntries(params).then((response) => {
86
- setAuthors(response?.items || []);
87
- setAuthorsLoading(false);
88
- });
89
- }, [debouncedAuthorSearch, portal]);
90
-
91
- useEffect(() => {
92
- if (selectedAuthor) {
93
- if (!authors.find(author => author.sys.id === selectedAuthor)) {
94
- setSelectedAuthor(null);
95
- }
96
- }
97
- }, [authors, selectedAuthor]);
98
-
99
- return (
100
- <div className="p-4 flex flex-col space-y-4">
101
- <div className="flex space-x-2">
102
- <div className="flex-grow">
103
- <TextInput
104
- width={"full"}
105
- placeholder={"Suche nach Titel"}
106
- onChange={(e) => setSearchQuery(e.target.value)}/>
107
- </div>
108
- <div className="flex-grow-0">
109
- <Autocomplete
110
- isLoading={authorsLoading}
111
- onSelectItem={item => setSelectedAuthor(item?.sys?.id)}
112
- items={authors}
113
- closeAfterSelect={true}
114
- itemToString={(item) => item.fields?.name?.de}
115
- renderItem={(item) => `${item.fields?.name?.de || "Unbekannter Name"}`}
116
- onInputValueChange={setAuthorSearchQuery}
117
-
118
- placeholder={'Autor'}
119
- noMatchesMessage={'Kein Autor gefunden'}
120
- >
121
- {(options) => {
122
- return options.map(option => <span key={option.value}>{option.label}</span>)
123
- }}
124
- </Autocomplete>
125
- </div>
126
- <div className="flex-grow-0">
127
- <Select value={selectedPortal} onChange={e => setSelectedPortal(e.target.value)}>
128
- {Object.keys(portals).filter(v => enabledPortals.includes(v)).sort().map(optionValue => <Select.Option
129
- key={optionValue}
130
- value={optionValue}>{portals[optionValue]}</Select.Option>)}
131
- </Select>
132
- </div>
133
- </div>
134
- <EntityList>
135
- {loading && <Spinner/>}
136
- {!loading && data.items.map((entry) => {
137
- const isCurrentlySelectedInOtherSlot = !! Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot !== slotId);
138
- const isCurrentlySelectedInSlot = !!Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot === slotId);
139
-
140
- let status = "draft";
141
- if (entry.sys.publishedAt) {
142
- status = "published";
143
- }
144
-
145
- let description = "";
146
- if (isCurrentlySelectedInSlot) {
147
- description = "Aktuell ausgewählt";
148
- } else if (isCurrentlySelectedInOtherSlot) {
149
- description = "Aktuell in anderem Teaser ausgewählt"
150
- }
151
-
152
- return <div
153
- key={entry.sys.id}
154
- className={classNames({
155
- "border-2 border-red-300": isCurrentlySelectedInSlot,
156
- "border-2 border-green-300": isCurrentlySelectedInOtherSlot,
157
- })}
158
- >
159
- <EntityList.Item
160
- isLoading={loading}
161
- entityType={format(new Date(entry.fields.teaserDate?.de || entry.fields.date?.de), "dd.MM.yyyy HH:mm")}
162
- title={entry.fields.title?.de}
163
- onClick={() => onEntryClick(entry)}
164
- thumbnailUrl={getArticleThumbnailUrl(entry)}
165
- description={description}
166
- status={status}
167
- />
168
- </div>
169
- })}
170
- </EntityList>
171
- <Pagination
172
- activePage={page}
173
- onPageChange={setPage}
174
- itemsPerPage={limit}
175
- isLastPage={data.total / limit <= page + 1}
176
- />
177
- </div>
178
- );
1
+ import {EntityList, Pagination, Spinner, Select, Autocomplete, TextInput} from "@contentful/f36-components";
2
+ import {getContentfulClient} from "../../../lib/contentfulClient";
3
+ import {useEffect, useState} from "react";
4
+ import format from "date-fns/format"
5
+ import useDebounce from "../../../hooks/useDebounce";
6
+ import classNames from "classnames";
7
+
8
+ export const NewestArticles = ({
9
+ slotState = {},
10
+ portals = {},
11
+ sdk,
12
+ onEntryClick = () => alert("onEntryClick missing"),
13
+ getArticleThumbnailUrl = (article) => alert("getArticleThumbnailUrl missing"),
14
+ }) => {
15
+ const {portal, slotId} = sdk.parameters.invocation;
16
+ const contentfulClient = getContentfulClient();
17
+ const limit = 25;
18
+ const [searchQuery, setSearchQuery] = useState("");
19
+ const [authorSearchQuery, setAuthorSearchQuery] = useState("");
20
+ const [enabledPortals, setEnabledPortals] = useState([portal]);
21
+ const [selectedPortal, setSelectedPortal] = useState(portal);
22
+ const [loading, setLoading] = useState(true);
23
+ const [data, setData] = useState({items: [], total: 0});
24
+ const [page, setPage] = useState(0);
25
+ const [selectedAuthor, setSelectedAuthor] = useState(null);
26
+ const [authors, setAuthors] = useState([]);
27
+ const [authorsLoading, setAuthorsLoading] = useState(false);
28
+
29
+ const debouncedSearch = useDebounce(searchQuery, 500);
30
+ const debouncedAuthorSearch = useDebounce(authorSearchQuery, 500);
31
+
32
+ useEffect(() => {
33
+ sdk.space.getContentType("article").then(articleType => {
34
+ let enabledPortals = articleType.fields.find(v => v.id === "portal")?.validations?.find(v => !!v.in)?.in;
35
+
36
+ if (enabledPortals) {
37
+ setEnabledPortals(enabledPortals);
38
+ }
39
+ });
40
+ }, []);
41
+
42
+ useEffect(() => {
43
+ setPage(0);
44
+ }, [searchQuery, selectedAuthor, selectedPortal]);
45
+
46
+ useEffect(() => {
47
+ setLoading(true);
48
+ const params = {
49
+ limit: limit,
50
+ skip: page * limit,
51
+ content_type: "article",
52
+ order: "-fields.teaserDate",
53
+ "fields.portal": selectedPortal,
54
+ select: [
55
+ "fields.title",
56
+ "fields.authors",
57
+ "fields.teaserDate",
58
+ "fields.image",
59
+ "fields.date",
60
+ "fields.portal",
61
+ "sys.publishedAt",
62
+ "sys.id",
63
+ ].join(","),
64
+ };
65
+
66
+ if (searchQuery) {
67
+ params["fields.title[match]"] = searchQuery;
68
+ }
69
+
70
+ if (selectedAuthor) {
71
+ params["fields.authors.sys.id"] = selectedAuthor;
72
+ }
73
+
74
+ contentfulClient.getEntries(params).then((response) => {
75
+ setLoading(false);
76
+ setData(response);
77
+ });
78
+ }, [debouncedSearch, selectedAuthor,selectedPortal, limit, page]);
79
+
80
+ useEffect(() => {
81
+ setAuthorsLoading(true);
82
+ const params = {
83
+ limit: 100,
84
+ content_type: "author",
85
+ order: "fields.name",
86
+ "fields.portal": portal,
87
+ };
88
+
89
+ if (debouncedAuthorSearch) {
90
+ params["fields.name[match]"] = debouncedAuthorSearch
91
+ } else {
92
+ setSelectedAuthor(null);
93
+ }
94
+
95
+ contentfulClient.getEntries(params).then((response) => {
96
+ setAuthors(response?.items || []);
97
+ setAuthorsLoading(false);
98
+ });
99
+ }, [debouncedAuthorSearch, portal]);
100
+
101
+ useEffect(() => {
102
+ if (selectedAuthor) {
103
+ if (!authors.find(author => author.sys.id === selectedAuthor)) {
104
+ setSelectedAuthor(null);
105
+ }
106
+ }
107
+ }, [authors, selectedAuthor]);
108
+
109
+ return (
110
+ <div className="p-4 flex flex-col space-y-4">
111
+ <div className="flex space-x-2">
112
+ <div className="flex-grow">
113
+ <TextInput
114
+ width={"full"}
115
+ placeholder={"Suche nach Titel"}
116
+ onChange={(e) => setSearchQuery(e.target.value)}/>
117
+ </div>
118
+ <div className="flex-grow-0">
119
+ <Autocomplete
120
+ isLoading={authorsLoading}
121
+ onSelectItem={item => setSelectedAuthor(item?.sys?.id)}
122
+ items={authors}
123
+ closeAfterSelect={true}
124
+ itemToString={(item) => item.fields?.name?.de}
125
+ renderItem={(item) => `${item.fields?.name?.de || "Unbekannter Name"}`}
126
+ onInputValueChange={setAuthorSearchQuery}
127
+
128
+ placeholder={'Autor'}
129
+ noMatchesMessage={'Kein Autor gefunden'}
130
+ >
131
+ {(options) => {
132
+ return options.map(option => <span key={option.value}>{option.label}</span>)
133
+ }}
134
+ </Autocomplete>
135
+ </div>
136
+ <div className="flex-grow-0">
137
+ <Select value={selectedPortal} onChange={e => setSelectedPortal(e.target.value)}>
138
+ {Object.keys(portals).filter(v => enabledPortals.includes(v)).sort().map(optionValue => <Select.Option
139
+ key={optionValue}
140
+ value={optionValue}>{portals[optionValue]}</Select.Option>)}
141
+ </Select>
142
+ </div>
143
+ </div>
144
+ <EntityList>
145
+ {loading && <Spinner/>}
146
+ {!loading && data.items.map((entry) => {
147
+ const isCurrentlySelectedInOtherSlot = !! Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot !== slotId);
148
+ const isCurrentlySelectedInSlot = !!Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot === slotId);
149
+
150
+ let status = "draft";
151
+ if (entry.sys.publishedAt) {
152
+ status = "published";
153
+ }
154
+
155
+ let description = "";
156
+ if (isCurrentlySelectedInSlot) {
157
+ description = "Aktuell ausgewählt";
158
+ } else if (isCurrentlySelectedInOtherSlot) {
159
+ description = "Aktuell in anderem Teaser ausgewählt"
160
+ }
161
+
162
+ return <div
163
+ key={entry.sys.id}
164
+ className={classNames({
165
+ "border-2 border-red-300": isCurrentlySelectedInSlot,
166
+ "border-2 border-green-300": isCurrentlySelectedInOtherSlot,
167
+ })}
168
+ >
169
+ <EntityList.Item
170
+ isLoading={loading}
171
+ entityType={format(new Date(entry.fields.teaserDate?.de || entry.fields.date?.de), "dd.MM.yyyy HH:mm")}
172
+ title={entry.fields.title?.de}
173
+ onClick={() => onEntryClick(entry)}
174
+ thumbnailUrl={getArticleThumbnailUrl(entry)}
175
+ description={description}
176
+ status={status}
177
+ />
178
+ </div>
179
+ })}
180
+ </EntityList>
181
+ <Pagination
182
+ activePage={page}
183
+ onPageChange={setPage}
184
+ itemsPerPage={limit}
185
+ isLastPage={data.total / limit <= page + 1}
186
+ />
187
+ </div>
188
+ );
179
189
  }
@@ -1,58 +1,58 @@
1
- import React, {
2
- useEffect, useState,
3
- } from 'react';
4
- import {NewestArticles} from "./Dialog/NewestArticles";
5
- import {NoAccess} from "../NoAccess";
6
-
7
- const Dialog = ({
8
- sdk,
9
- portals,
10
- getArticleThumbnailUrl,
11
- }) => {
12
- const [slotState, setSlotState] = useState({});
13
-
14
- const selectEntry = (entry) => {
15
- sdk.close(entry);
16
- };
17
-
18
- const loadSlotStateForPage = async () => {
19
- try {
20
- const apiRoot = sdk.parameters.instance.apiRoot;
21
- const portal = sdk.parameters.invocation.portal;
22
- const pageId = sdk.parameters.invocation.entryId;
23
- const date = sdk.parameters.invocation.date;
24
- const teasermanagerUrl = `${apiRoot}/api/findStateForPage?project=${portal}&page=${pageId}&date=${date.toISOString()}`;
25
- const response = await fetch(teasermanagerUrl).then(res => res.json());
26
-
27
- if (response?.message) {
28
- console.error(response.message);
29
- }
30
-
31
- return response?.data || {};
32
- } catch (e) {
33
- console.error(e);
34
- }
35
- };
36
-
37
- useEffect(() => {
38
- loadSlotStateForPage().then(setSlotState);
39
- }, [sdk.parameters.invocation]);
40
-
41
- return <div style={{
42
- backgroundColor: "#FFFFFF",
43
- minHeight: "100vh",
44
- }}>
45
- <NewestArticles sdk={sdk} onEntryClick={selectEntry} slotState={slotState} getArticleThumbnailUrl={getArticleThumbnailUrl} portals={portals}/>
46
- {/*
47
- <Tabs defaultTab="newest">
48
- <Tabs.List>
49
- <Tabs.Tab panelId="newest">Neuste Artikel</Tabs.Tab>
50
- </Tabs.List>
51
- <Tabs.Panel id="newest">
52
- </Tabs.Panel>
53
- </Tabs>
54
- */}
55
- </div>;
56
- };
57
-
58
- export default Dialog;
1
+ import React, {
2
+ useEffect, useState,
3
+ } from 'react';
4
+ import {NewestArticles} from "./Dialog/NewestArticles";
5
+ import {NoAccess} from "../NoAccess";
6
+
7
+ const Dialog = ({
8
+ sdk,
9
+ portals,
10
+ getArticleThumbnailUrl,
11
+ }) => {
12
+ const [slotState, setSlotState] = useState({});
13
+
14
+ const selectEntry = (entry) => {
15
+ sdk.close(entry);
16
+ };
17
+
18
+ const loadSlotStateForPage = async () => {
19
+ try {
20
+ const apiRoot = sdk.parameters.instance.apiRoot;
21
+ const portal = sdk.parameters.invocation.portal;
22
+ const pageId = sdk.parameters.invocation.entryId;
23
+ const date = sdk.parameters.invocation.date;
24
+ const teasermanagerUrl = `${apiRoot}/api/findStateForPage?project=${portal}&page=${pageId}&date=${date.toISOString()}`;
25
+ const response = await fetch(teasermanagerUrl).then(res => res.json());
26
+
27
+ if (response?.message) {
28
+ console.error(response.message);
29
+ }
30
+
31
+ return response?.data || {};
32
+ } catch (e) {
33
+ console.error(e);
34
+ }
35
+ };
36
+
37
+ useEffect(() => {
38
+ loadSlotStateForPage().then(setSlotState);
39
+ }, [sdk.parameters.invocation]);
40
+
41
+ return <div style={{
42
+ backgroundColor: "#FFFFFF",
43
+ minHeight: "100vh",
44
+ }}>
45
+ <NewestArticles sdk={sdk} onEntryClick={selectEntry} slotState={slotState} getArticleThumbnailUrl={getArticleThumbnailUrl} portals={portals}/>
46
+ {/*
47
+ <Tabs defaultTab="newest">
48
+ <Tabs.List>
49
+ <Tabs.Tab panelId="newest">Neuste Artikel</Tabs.Tab>
50
+ </Tabs.List>
51
+ <Tabs.Panel id="newest">
52
+ </Tabs.Panel>
53
+ </Tabs>
54
+ */}
55
+ </div>;
56
+ };
57
+
58
+ export default Dialog;