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 && ( +
+
+ + +
+
+
+ +
+
+ +
+
+
+ )} +
+ +
+
+

+ {alertHeadlineText} +

+

+ {alertText} +

+