diff --git a/meinberlin/assets/scss/components_user_facing/_search-profiles.scss b/meinberlin/assets/scss/components_user_facing/_search-profiles.scss
index 92de8111f7..ece6d1bb37 100644
--- a/meinberlin/assets/scss/components_user_facing/_search-profiles.scss
+++ b/meinberlin/assets/scss/components_user_facing/_search-profiles.scss
@@ -18,10 +18,28 @@
}
.search-profile__title {
- margin-bottom: 0;
+ margin-bottom: 0.25rem;
margin-top: 0;
}
+.search-profile__title:last-child {
+ margin-bottom: 0;
+}
+
+.search-profile__filters {
+ color: var(--color-grey-darkest);
+ font-size: 0.9rem;
+ margin-bottom: 0;
+ padding-left: 0;
+}
+
+.search-profile__filter {
+ display: inline-block;
+ list-style: none;
+ margin-right: 1rem;
+ margin-bottom: 0;
+}
+
.search-profile__header {
display: flex;
gap: 3rem;
@@ -50,11 +68,10 @@
display: inline-block;
text-align: center;
cursor: pointer;
- color: var(--color-black);
+ color: $text-base;
}
-.search-profile__button:hover,
-.search-profile__button:focus {
+.search-profile__button:hover {
text-decoration: underline;
}
@@ -96,22 +113,9 @@
}
.search-profile__alert {
- left: 0;
- right: 0;
- pointer-events: none;
- position: fixed;
- padding-left: 0.75rem;
- padding-right: 0.75rem;
- top: 0;
- width: 100%;
- z-index: 50;
-}
-
-.search-profile__alert-container {
- margin: 0 auto;
- pointer-events: auto;
+ margin-top: -1.5em;
@media screen and (min-width: $breakpoint-tablet-landscape) {
- width: clamp(980px, 50vw, 100%);
+ margin-top: -2.5em;
}
}
\ No newline at end of file
diff --git a/meinberlin/react/kiezradar/SearchProfile.jsx b/meinberlin/react/kiezradar/SearchProfile.jsx
new file mode 100644
index 0000000000..e7e6e7849c
--- /dev/null
+++ b/meinberlin/react/kiezradar/SearchProfile.jsx
@@ -0,0 +1,137 @@
+import React, { useState } from 'react'
+import django from 'django'
+import SearchProfileButtons from './SearchProfileButtons'
+import { updateItem } from '../contrib/helpers'
+
+const renameSearchProfileText = django.gettext('Rename search profile')
+const cancelText = django.gettext('Cancel')
+const saveText = django.gettext('Save')
+const savingText = django.gettext('Saving')
+const viewProjectsText = django.gettext('View projects')
+const errorText = django.gettext('Error')
+const errorDeleteSearchProfilesText = django.gettext(
+ 'Failed to delete search profile'
+)
+const errorUpdateSearchProfilesText = django.gettext(
+ 'Failed to update search profile'
+)
+
+export default function SearchProfile ({ apiUrl, planListUrl, profile: profile_, onDelete }) {
+ const [isEditing, setIsEditing] = useState(false)
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState(null)
+ const [profile, setProfile] = useState(profile_)
+
+ const handleDelete = async () => {
+ setLoading(true)
+ setError(null)
+
+ try {
+ const response = await updateItem({}, apiUrl + profile.id + '/', 'DELETE')
+
+ if (!response.ok) {
+ throw new Error(errorDeleteSearchProfilesText)
+ }
+
+ onDelete(profile.id)
+ } catch (err) {
+ setError(err.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const handleSubmit = async (e) => {
+ e.preventDefault()
+ setLoading(true)
+ setError(null)
+
+ try {
+ const response = await updateItem({ name: e.target.elements.name.value }, apiUrl + profile.id + '/', 'PATCH')
+
+ if (!response.ok) {
+ throw new Error(errorUpdateSearchProfilesText)
+ }
+
+ const data = await response.json()
+ setProfile(data)
+ } catch (err) {
+ setError(err.message)
+ } finally {
+ setIsEditing(false)
+ setLoading(false)
+ }
+ }
+
+ const filters = [
+ profile.districts,
+ profile.project_types,
+ profile.topics,
+ profile.organisations
+ ]
+ .map((filter) => filter.map(({ name }) => name))
+ .map((names) => names.join(', '))
+
+ return (
+
+
+
+
{profile.name}
+
+ {filters.map((filter) => (
+ - {filter}
+ ))}
+
+
+ {!isEditing && (
+
+ setIsEditing(true)}
+ onDelete={handleDelete}
+ loading={loading}
+ />
+
+ )}
+
+ {error &&
{errorText + ': ' + error}
}
+ {isEditing && (
+
+ )}
+
+
+ {viewProjectsText}
+
+ {!isEditing && (
+
+ setIsEditing(true)}
+ onDelete={handleDelete}
+ loading={loading}
+ />
+
+ )}
+
+
+ )
+}
diff --git a/meinberlin/react/kiezradar/SearchProfileAlert.jsx b/meinberlin/react/kiezradar/SearchProfileAlert.jsx
new file mode 100644
index 0000000000..68658da300
--- /dev/null
+++ b/meinberlin/react/kiezradar/SearchProfileAlert.jsx
@@ -0,0 +1,37 @@
+import React from 'react'
+import django from 'django'
+
+const alertHeadlineText = django.gettext(
+ 'Search profile successfully deleted'
+)
+const alertText = django.gettext('Your changes have been deleted.')
+
+export default function SearchProfileAlert ({ onClose }) {
+ return (
+
+
+
+
+ {alertHeadlineText}
+
+
+ {alertText}
+
+
+
+
+
+ )
+}
diff --git a/meinberlin/react/kiezradar/SearchProfileButtons.jsx b/meinberlin/react/kiezradar/SearchProfileButtons.jsx
new file mode 100644
index 0000000000..9699025252
--- /dev/null
+++ b/meinberlin/react/kiezradar/SearchProfileButtons.jsx
@@ -0,0 +1,24 @@
+import React from 'react'
+import django from 'django'
+
+const renameText = django.gettext('Rename')
+const deleteText = django.gettext('Delete')
+
+export default function SearchProfileButtons ({ onEdit, onDelete, loading }) {
+ return (
+
+
+
+
+ )
+}
diff --git a/meinberlin/react/kiezradar/SearchProfileList.jsx b/meinberlin/react/kiezradar/SearchProfileList.jsx
new file mode 100644
index 0000000000..0db1eeb669
--- /dev/null
+++ b/meinberlin/react/kiezradar/SearchProfileList.jsx
@@ -0,0 +1,82 @@
+import React, { useState, useEffect } from 'react'
+import django from 'django'
+import Spinner from '../contrib/Spinner'
+import SearchProfile from './SearchProfile'
+
+const noSavedProfilesText = django.gettext('No saved search profiles')
+const findProjectsText = django.gettext('Find projects')
+const yourSavedProfilesText = django.gettext('Your saved search profiles')
+const errorText = django.gettext('Error')
+const errorSearchProfilesText = django.gettext(
+ 'Failed to fetch search profiles'
+)
+
+export default function SearchProfileList ({ apiUrl, planListUrl, onAlert }) {
+ const [searchProfiles, setSearchProfiles] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ useEffect(() => {
+ const fetchSearchProfiles = async () => {
+ try {
+ setLoading(true)
+ setError(null)
+
+ const response = await fetch(apiUrl)
+
+ if (!response.ok) {
+ throw new Error(errorSearchProfilesText)
+ }
+
+ const data = await response.json()
+ setSearchProfiles(data)
+ } catch (err) {
+ setError(err.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+ fetchSearchProfiles()
+ }, [])
+
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return (
+
+ {errorText}: {error}
+
+ )
+ }
+
+ return (
+ <>
+ {searchProfiles.length === 0 ? noSavedProfilesText : yourSavedProfilesText + ' ' + searchProfiles.length}
+ {searchProfiles.length === 0
+ ? (
+
+
+ {findProjectsText}
+
+ )
+ : (
+ searchProfiles.map((profile) => (
+ {
+ onAlert()
+ setSearchProfiles((prevSearchProfiles) =>
+ prevSearchProfiles.filter((profile) => profile.id !== id)
+ )
+ }}
+ />
+ ))
+ )}
+ >
+ )
+}
diff --git a/meinberlin/react/kiezradar/SearchProfiles.jsx b/meinberlin/react/kiezradar/SearchProfiles.jsx
index 7d0d325d1a..2f741f6205 100644
--- a/meinberlin/react/kiezradar/SearchProfiles.jsx
+++ b/meinberlin/react/kiezradar/SearchProfiles.jsx
@@ -1,294 +1,22 @@
-import cookie from 'js-cookie'
-import React, { useState, useEffect } from 'react'
+import React, { useState } from 'react'
import django from 'django'
-import Spinner from '../contrib/Spinner'
+import SearchProfileList from './SearchProfileList'
+import SearchProfileAlert from './SearchProfileAlert'
const titleText = django.gettext('Search Profiles')
const descriptionText = django.gettext(
'In this area you manage your search profiles.'
)
-const noSavedProfilesText = django.gettext('No saved search profiles')
-const findProjectsText = django.gettext('Find projects')
-const yourSavedProfilesText = django.gettext('Your saved search profiles')
-const renameText = django.gettext('Rename')
-const deleteText = django.gettext('Delete')
-const renameSearchProfileText = django.gettext('Rename search profile')
-const cancelText = django.gettext('Cancel')
-const saveText = django.gettext('Save')
-const savingText = django.gettext('Saving')
-const viewProjectsText = django.gettext('View projects')
-const errorText = django.gettext('Error')
-const errorSearchProfilesText = django.gettext(
- 'Failed to fetch search profiles'
-)
-const errorDeleteSearchProfilesText = django.gettext(
- 'Failed to delete search profile'
-)
-const errorUpdateSearchProfilesText = django.gettext(
- 'Failed to update search profile'
-)
-const alertHeadlineText = django.gettext(
- 'Search profile successfully deleted'
-)
-const alertText = django.gettext(
- 'Your changes have been deleted.'
-)
export default function SearchProfiles (props) {
- return (
- <>
- {titleText}
- {descriptionText}
-
- >
- )
-}
-
-function SearchProfileList ({ apiUrl, planListUrl }) {
const [alert, setAlert] = useState(false)
- const [searchProfiles, setSearchProfiles] = useState([])
- const [loading, setLoading] = useState(true)
- const [error, setError] = useState(null)
-
- useEffect(() => {
- const fetchSearchProfiles = async () => {
- try {
- setLoading(true)
- setError(null)
-
- const response = await fetch(apiUrl)
-
- if (!response.ok) {
- throw new Error(errorSearchProfilesText)
- }
-
- const data = await response.json()
- setSearchProfiles(data)
- } catch (err) {
- setError(err.message)
- } finally {
- setLoading(false)
- }
- }
- fetchSearchProfiles()
- }, [])
-
- if (loading) {
- return
- }
-
- if (error) {
- return (
-
- {errorText}: {error}
-
- )
- }
return (
<>
- {alert && setAlert(false)} />}
- {searchProfiles.length === 0
- ? (
- <>
- {noSavedProfilesText}
-
-
- {findProjectsText}
-
- >
- )
- : (
- <>
-
- {yourSavedProfilesText} ({searchProfiles.length})
-
- {searchProfiles.map((profile) => (
- {
- setAlert(true)
- setSearchProfiles((prevSearchProfiles) =>
- prevSearchProfiles.filter((profile) => profile.id !== id)
- )
- }}
- />
- ))}
- >
- )}
+ {alert && setAlert(false)} />}
+ {titleText}
+ {descriptionText}
+ setAlert(true)} />
>
)
}
-
-function SearchProfile ({ apiUrl, planListUrl, profile: profile_, onDelete }) {
- const [isEditing, setIsEditing] = useState(false)
- const [loading, setLoading] = useState(false)
- const [error, setError] = useState(null)
- const [profile, setProfile] = useState(profile_)
-
- const handleDelete = async () => {
- setLoading(true)
- setError(null)
-
- try {
- const response = await fetch(apiUrl + profile.id + '/', {
- headers: {
- 'X-CSRFToken': cookie.get('csrftoken')
- },
- method: 'DELETE'
- })
-
- if (!response.ok) {
- throw new Error(errorDeleteSearchProfilesText)
- }
-
- onDelete(profile.id)
- } catch (err) {
- setError(err.message)
- } finally {
- setLoading(false)
- }
- }
-
- const handleSubmit = async (e) => {
- e.preventDefault()
- setLoading(true)
- setError(null)
-
- try {
- const response = await fetch(apiUrl + profile.id + '/', {
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- 'X-CSRFToken': cookie.get('csrftoken')
- },
- method: 'PATCH',
- body: JSON.stringify({ name: e.target.elements.name.value })
- })
-
- if (!response.ok) {
- throw new Error(errorUpdateSearchProfilesText)
- }
-
- const data = await response.json()
- setProfile(data)
- } catch (err) {
- setError(err.message)
- } finally {
- setIsEditing(false)
- setLoading(false)
- }
- }
-
- return (
-
-
-
{profile.name}
- {!isEditing && (
-
- setIsEditing(true)}
- onDelete={handleDelete}
- loading={loading}
- />
-
- )}
-
- {error &&
{errorText + ': ' + error}
}
- {isEditing && (
-
- )}
-
-
- {viewProjectsText}
-
- {!isEditing && (
-
- setIsEditing(true)}
- onDelete={handleDelete}
- loading={loading}
- />
-
- )}
-
-
- )
-}
-
-function Buttons ({ onEdit, onDelete, loading }) {
- return (
-
-
-
-
- )
-}
-
-function Alert ({ onClose }) {
- return (
-
-
-
-
-
- {alertHeadlineText}
-
-
- {alertText}
-
-
-
-
-
-
- )
-}