Compare commits

...

1 Commits

Author SHA1 Message Date
Johannes eaa7771dec UX tweaks based on user feedback 2026-05-15 14:19:57 +02:00
26 changed files with 262 additions and 141 deletions
@@ -106,18 +106,20 @@ export const ResponseCardModal = ({
</DialogDescription>
</VisuallyHidden>
<DialogBody>
<SingleResponseCard
survey={survey}
response={responses[currentIndex]}
user={user}
environment={environment}
environmentTags={environmentTags}
isReadOnly={isReadOnly}
updateResponse={updateResponse}
updateResponseList={updateResponseList}
setSelectedResponseId={setSelectedResponseId}
locale={locale}
/>
<div className="my-3">
<SingleResponseCard
survey={survey}
response={responses[currentIndex]}
user={user}
environment={environment}
environmentTags={environmentTags}
isReadOnly={isReadOnly}
updateResponse={updateResponse}
updateResponseList={updateResponseList}
setSelectedResponseId={setSelectedResponseId}
locale={locale}
/>
</div>
</DialogBody>
<DialogFooter>
<Button
@@ -11,6 +11,7 @@ import { renderHyperlinkedContent } from "@/modules/analysis/utils";
import { PersonAvatar } from "@/modules/ui/components/avatars";
import { Button } from "@/modules/ui/components/button";
import { EmptyState } from "@/modules/ui/components/empty-state";
import { IdBadge } from "@/modules/ui/components/id-badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/modules/ui/components/table";
import { ElementSummaryHeader } from "./ElementSummaryHeader";
@@ -47,7 +48,8 @@ export const OpenTextSummary = ({ elementSummary, environmentId, survey, locale
<TableRow>
<TableHead className="w-1/4">{t("common.user")}</TableHead>
<TableHead className="w-2/4">{t("common.response")}</TableHead>
<TableHead className="w-1/4">{t("common.time")}</TableHead>
<TableHead className="w-1/6">{t("common.time")}</TableHead>
<TableHead className="w-1/6">{t("common.response_id")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -79,9 +81,12 @@ export const OpenTextSummary = ({ elementSummary, environmentId, survey, locale
? renderHyperlinkedContent(response.value)
: response.value}
</TableCell>
<TableCell className="w-1/4">
<TableCell className="w-1/6">
{timeSince(new Date(response.updatedAt).toISOString(), locale)}
</TableCell>
<TableCell className="w-1/6">
<IdBadge id={response.id} />
</TableCell>
</TableRow>
))}
</TableBody>
+9 -3
View File
@@ -214,6 +214,9 @@ checksums:
common/failed_to_load_organizations: 512808a2b674c7c28bca73f8f91fd87e
common/failed_to_load_workspaces: 6ee3448097394517dc605074cd4e6ea4
common/field_placeholder: ec26d96643d86da164162204ec6c650f
common/file_size_must_be_less_than_5_mb: a7d8ef9f888bfb3de2b589947fa2a63f
common/file_storage_not_set_up: ed82fc9c54da2f16245eaea9f8aa08f3
common/file_upload_service_unavailable: 93a6a904cef89cc18d2c4a65e2d581cc
common/filter: 626325a05e4c8800f7ede7012b0cadaf
common/finish: ffa7a10f71182b48fefed7135bee24fa
common/first_name: cf040a5d6a9fd696be400380cc99f54b
@@ -432,7 +435,6 @@ checksums:
common/trial_one_day_remaining: 2d64d39fca9589c4865357817bcc24d5
common/try_again: 33dd8820e743e35a66e6977f69e9d3b5
common/type: f04471a7ddac844b9ad145eb9911ef75
common/unknown_survey: dd8f6985e17ccf19fac1776e18b2c498
common/unlock_more_workspaces_with_a_higher_plan: fe1590075b855bb4306c9388b65143b0
common/update: 079fc039262fd31b10532929685c2d1b
common/updated: 8aa8ff2dc2977ca4b269e80a513100b4
@@ -677,6 +679,7 @@ checksums:
environments/contacts/select_a_survey: 1f49086dfb874307aae1136e88c3d514
environments/contacts/select_attribute: d93fb60eb4fbb42bf13a22f6216fbd79
environments/contacts/select_attribute_key: 673a6683fab41b387d921841cded7e38
environments/contacts/survey_response_created: e4a6eaac2acd8defca3ff57eae045ba6
environments/contacts/survey_viewed: 646d413218626787b0373ffd71cb7451
environments/contacts/survey_viewed_at: 2ab535237af5c3c3f33acc792a7e70a4
environments/contacts/system_attributes: eadb6a8888c7b32c0e68881f945ae9b6
@@ -1809,6 +1812,7 @@ checksums:
environments/surveys/responses/completed: 2dca9d2e4c0fe669801112a66d679f6d
environments/surveys/responses/country: 73581fc33a1e83e6a56db73558e7b5c6
environments/surveys/responses/decrement_quotas: 9ef63d3e9214c508eb040eb41c25a5c4
environments/surveys/responses/delete_response: d86cb6fe4af953cde58f12092962bca4
environments/surveys/responses/delete_response_confirmation: 83a43954ca60c8ef30b9683253a6f5ea
environments/surveys/responses/delete_response_quotas: 6719c90d8019000ca0a74586fb3b6a65
environments/surveys/responses/device: c009f849d689c745de9e38ad17644c7a
@@ -1833,8 +1837,10 @@ checksums:
environments/surveys/responses/this_response_is_in_progress: 7d785fcb597ea30466467084fd474904
environments/surveys/responses/zip_post_code: ab7dc45bd5f9e37930586e2db17e4304
environments/surveys/search_by_survey_name: 44cf2e6f8ba43d233fb33939431eba99
environments/surveys/share/anonymous_links/custom_single_use_id_description: 53c6d2b6cf597115b1f5a280ce7e9cab
environments/surveys/share/anonymous_links/custom_single_use_id_title: 9d708fe4ced64ddedd307fb61827cd03
environments/surveys/share/anonymous_links/custom_single_use_id_description: 994c76257cc373388a3870caf9d85dc7
environments/surveys/share/anonymous_links/custom_single_use_id_placeholder: e089b0f1838164b2ef2f74076477d8f2
environments/surveys/share/anonymous_links/custom_single_use_id_required: 36f60e76ebed5af11661ac7bbadd9a2b
environments/surveys/share/anonymous_links/custom_single_use_id_title: 0a1b97ed6fd82d9b0f9f43750d45fbe3
environments/surveys/share/anonymous_links/custom_start_point: 4ea6552b37339d17e02f3bce8c7e4125
environments/surveys/share/anonymous_links/data_prefilling: 82f0e31e90f1f2ca31361df9893e117c
environments/surveys/share/anonymous_links/description: d13534a22f135420651ebfd218a4f01b
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "Noch 1 Tag in deiner Testphase",
"try_again": "Versuch's nochmal",
"type": "Typ",
"unknown_survey": "Unbekannte Umfrage",
"unlock_more_workspaces_with_a_higher_plan": "Schalten Sie mehr Projekte mit einem höheren Tarif frei.",
"update": "Aktualisierung",
"updated": "Aktualisiert",
@@ -716,6 +715,7 @@
"select_a_survey": "Wähle eine Umfrage aus",
"select_attribute": "Attribut auswählen",
"select_attribute_key": "Attributschlüssel auswählen",
"survey_response_created": "Antwort erstellt",
"survey_viewed": "Umfrage angesehen",
"survey_viewed_at": "Angesehen am",
"system_attributes": "Systemattribute",
@@ -1891,6 +1891,7 @@
"completed": "Erledigt ✅",
"country": "Land",
"decrement_quotas": "Alle Grenzwerte der Kontingente einschließlich dieser Antwort verringern",
"delete_response": "Antwort löschen",
"delete_response_confirmation": "Dies wird die Umfrageantwort einschließlich aller Antworten, Tags, angehängter Dokumente und Antwort-Metadaten löschen.",
"delete_response_quotas": "Die Antwort ist Teil der Quoten für diese Umfrage. Wie möchten Sie die Quoten verwalten?",
"device": "Gerät",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Nach Umfragenamen suchen",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Erstelle eine lesbare Einmal-ID und kopiere einen signierten Link dafür.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Verwende eine benutzerdefinierte Einmal-ID in der URL.",
"custom_start_point": "Benutzerdefinierter Startpunkt",
"data_prefilling": "Daten-Prefilling",
"description": "Antworten, die von diesen Links kommen, werden anonym",
+2 -1
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 day left in your trial",
"try_again": "Try again",
"type": "Type",
"unknown_survey": "Unknown survey",
"unlock_more_workspaces_with_a_higher_plan": "Unlock more workspaces with a higher plan.",
"update": "Update",
"updated": "Updated",
@@ -716,6 +715,7 @@
"select_a_survey": "Select a survey",
"select_attribute": "Select Attribute",
"select_attribute_key": "Select attribute key",
"survey_response_created": "Response created",
"survey_viewed": "Survey viewed",
"survey_viewed_at": "Viewed At",
"system_attributes": "System Attributes",
@@ -1891,6 +1891,7 @@
"completed": "Completed ✅",
"country": "Country",
"decrement_quotas": "Decrement all limits of quotas including this response",
"delete_response": "Delete response",
"delete_response_confirmation": "This will delete the survey response, including all answers, tags, attached documents, and response metadata.",
"delete_response_quotas": "The response is part of quotas for this survey. How do you want to handle the quotas?",
"device": "Device",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 día restante en tu prueba",
"try_again": "Intentar de nuevo",
"type": "Tipo",
"unknown_survey": "Encuesta desconocida",
"unlock_more_workspaces_with_a_higher_plan": "Desbloquea más proyectos con un plan superior.",
"update": "Actualizar",
"updated": "Actualizado",
@@ -716,6 +715,7 @@
"select_a_survey": "Selecciona una encuesta",
"select_attribute": "Seleccionar atributo",
"select_attribute_key": "Seleccionar clave de atributo",
"survey_response_created": "Respuesta creada",
"survey_viewed": "Encuesta vista",
"survey_viewed_at": "Vista el",
"system_attributes": "Atributos del sistema",
@@ -1891,6 +1891,7 @@
"completed": "Completado ✅",
"country": "País",
"decrement_quotas": "Reducir todos los límites de cuotas que incluyen esta respuesta",
"delete_response": "Eliminar respuesta",
"delete_response_confirmation": "Esto eliminará la respuesta de la encuesta, incluyendo todas las respuestas, etiquetas, documentos adjuntos y metadatos de respuesta.",
"delete_response_quotas": "La respuesta forma parte de cuotas para esta encuesta. ¿Cómo quieres gestionar las cuotas?",
"device": "Dispositivo",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Buscar por nombre de encuesta",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Crea un ID legible de un solo uso y copia un enlace firmado para él.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Usa un ID personalizado de un solo uso en la URL.",
"custom_start_point": "Punto de inicio personalizado",
"data_prefilling": "Prellenado de datos",
"description": "Las respuestas procedentes de estos enlaces serán anónimas",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 jour restant dans votre période d'essai",
"try_again": "Réessayer",
"type": "Type",
"unknown_survey": "Enquête inconnue",
"unlock_more_workspaces_with_a_higher_plan": "Débloquez plus de projets avec un forfait supérieur.",
"update": "Mise à jour",
"updated": "Mise à jour",
@@ -716,6 +715,7 @@
"select_a_survey": "Sélectionner une enquête",
"select_attribute": "Sélectionner un attribut",
"select_attribute_key": "Sélectionner une clé d'attribut",
"survey_response_created": "Réponse créée",
"survey_viewed": "Enquête consultée",
"survey_viewed_at": "Consultée le",
"system_attributes": "Attributs système",
@@ -1891,6 +1891,7 @@
"completed": "Terminé ✅",
"country": "Pays",
"decrement_quotas": "Décrémentez toutes les limites des quotas y compris cette réponse",
"delete_response": "Supprimer la réponse",
"delete_response_confirmation": "Cela supprimera la réponse au sondage, y compris toutes les réponses, les étiquettes, les documents joints et les métadonnées de réponse.",
"delete_response_quotas": "La réponse fait partie des quotas pour ce sondage. Comment voulez-vous gérer les quotas ?",
"device": "Dispositif",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Recherche par nom d'enquête",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Créez un identifiant à usage unique lisible et copiez un lien signé pour celui-ci.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Utilisez un identifiant personnalisé à usage unique dans l'URL.",
"custom_start_point": "Point de départ personnalisé",
"data_prefilling": "Préremplissage des données",
"description": "Les réponses provenant de ces liens seront anonymes",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 nap van hátra a próbaidőszakából",
"try_again": "Próbálja újra",
"type": "Típus",
"unknown_survey": "Ismeretlen kérdőív",
"unlock_more_workspaces_with_a_higher_plan": "Több munkaterület feloldása egy magasabb csomaggal.",
"update": "Frissítés",
"updated": "Frissítve",
@@ -716,6 +715,7 @@
"select_a_survey": "Kérdőív kiválasztása",
"select_attribute": "Attribútum kiválasztása",
"select_attribute_key": "Attribútum kulcsának kiválasztása",
"survey_response_created": "Válasz létrehozva",
"survey_viewed": "Kérdőív megtekintve",
"survey_viewed_at": "Megtekintve ekkor:",
"system_attributes": "Rendszerattribútumok",
@@ -1891,6 +1891,7 @@
"completed": "Befejezve ✅",
"country": "Ország",
"decrement_quotas": "A kvóták összes korlátjának csökkentése, beleértve ezt a választ is",
"delete_response": "Válasz törlése",
"delete_response_confirmation": "Ez törölni fogja a kérdőívre adott választ, beleértve az összes választ, címkét, csatolt dokumentumot és a válasz metaadatait.",
"delete_response_quotas": "A válasz a kérdőív kvótáinak részét képezik. Hogyan szeretné kezelni a kvótákat?",
"device": "Eszköz",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Keresés kérdőívnév alapján",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Hozzon létre egy olvasható, egyszer használatos azonosítót, és másoljon ki egy aláírt linket hozzá.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Használjon egyedi, egyszer használatos azonosítót az URL-ben.",
"custom_start_point": "Egyéni kezdési pont",
"data_prefilling": "Adatok előre kitöltése",
"description": "Az ezekről a hivatkozásokról érkező válaszok névtelenek lesznek",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "トライアル期間の残り1日",
"try_again": "もう一度お試しください",
"type": "種類",
"unknown_survey": "不明なフォーム",
"unlock_more_workspaces_with_a_higher_plan": "上位プランでより多くのワークスペースを利用できます。",
"update": "更新",
"updated": "更新済み",
@@ -716,6 +715,7 @@
"select_a_survey": "フォームを選択",
"select_attribute": "属性を選択",
"select_attribute_key": "属性キーを選択",
"survey_response_created": "回答が作成されました",
"survey_viewed": "フォームを閲覧",
"survey_viewed_at": "閲覧日時",
"system_attributes": "システム属性",
@@ -1891,6 +1891,7 @@
"completed": "完了 ✅",
"country": "国",
"decrement_quotas": "すべて の 制限 を 減少 し、 この 回答 を 含む しきい値",
"delete_response": "回答を削除",
"delete_response_confirmation": "これにより、すべての回答、タグ、添付されたドキュメント、および回答メタデータを含むフォームの回答が削除されます。",
"delete_response_quotas": "この回答は、このアンケートの割り当ての一部です。 割り当てをどのように処理しますか?",
"device": "デバイス",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "フォーム名で検索",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "読みやすいワンタイムIDを作成し、それに対応する署名付きリンクをコピーします。",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "URLにカスタムワンタイムIDを使用する。",
"custom_start_point": "カスタム開始点",
"data_prefilling": "データの事前入力",
"description": "これらのリンクからの回答は匿名になります",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 dag over in je proefperiode",
"try_again": "Probeer het opnieuw",
"type": "Type",
"unknown_survey": "Onbekende enquête",
"unlock_more_workspaces_with_a_higher_plan": "Ontgrendel meer werkruimtes met een hoger abonnement.",
"update": "Update",
"updated": "Bijgewerkt",
@@ -716,6 +715,7 @@
"select_a_survey": "Selecteer een enquête",
"select_attribute": "Selecteer Kenmerk",
"select_attribute_key": "Selecteer kenmerksleutel",
"survey_response_created": "Antwoord aangemaakt",
"survey_viewed": "Enquête bekeken",
"survey_viewed_at": "Bekeken op",
"system_attributes": "Systeemkenmerken",
@@ -1891,6 +1891,7 @@
"completed": "Voltooid ✅",
"country": "Land",
"decrement_quotas": "Verlaag alle limieten van quota, inclusief dit antwoord",
"delete_response": "Antwoord verwijderen",
"delete_response_confirmation": "Hierdoor wordt het enquêteantwoord verwijderd, inclusief alle antwoorden, tags, bijgevoegde documenten en metagegevens van het antwoord.",
"delete_response_quotas": "De respons maakt deel uit van de quota voor dit onderzoek. Hoe wilt u omgaan met de quota?",
"device": "Apparaat",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Zoek op enquêtenaam",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Maak een leesbare eenmalige ID aan en kopieer een ondertekende link hiervoor.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Gebruik een aangepaste eenmalige ID in de URL.",
"custom_start_point": "Aangepast startpunt",
"data_prefilling": "Gegevens vooraf invullen",
"description": "Reacties afkomstig van deze links zijn anoniem",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 dia restante no seu período de teste",
"try_again": "Tenta de novo",
"type": "Tipo",
"unknown_survey": "Pesquisa desconhecida",
"unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.",
"update": "atualizar",
"updated": "atualizado",
@@ -716,6 +715,7 @@
"select_a_survey": "Selecione uma pesquisa",
"select_attribute": "Selecionar Atributo",
"select_attribute_key": "Selecionar chave de atributo",
"survey_response_created": "Resposta criada",
"survey_viewed": "Pesquisa visualizada",
"survey_viewed_at": "Visualizada em",
"system_attributes": "Atributos do sistema",
@@ -1891,6 +1891,7 @@
"completed": "Concluído ✅",
"country": "País",
"decrement_quotas": "Diminua todos os limites de cotas, incluindo esta resposta",
"delete_response": "Excluir resposta",
"delete_response_confirmation": "Isso irá excluir a resposta da pesquisa, incluindo todas as respostas, etiquetas, documentos anexados e metadados da resposta.",
"delete_response_quotas": "A resposta faz parte das cotas desta pesquisa. Como você quer gerenciar as cotas?",
"device": "dispositivo",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Buscar pelo nome da pesquisa",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Crie um ID de uso único legível e copie um link assinado para ele.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Use um ID de uso único personalizado na URL.",
"custom_start_point": "Ponto de início personalizado",
"data_prefilling": "preenchimento automático de dados",
"description": "Respostas vindas desses links serão anônimas",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 dia restante no teu período de teste",
"try_again": "Tente novamente",
"type": "Tipo",
"unknown_survey": "Inquérito desconhecido",
"unlock_more_workspaces_with_a_higher_plan": "Desbloqueie mais projetos com um plano superior.",
"update": "Atualizar",
"updated": "Atualizado",
@@ -716,6 +715,7 @@
"select_a_survey": "Selecione um inquérito",
"select_attribute": "Selecionar Atributo",
"select_attribute_key": "Selecionar chave de atributo",
"survey_response_created": "Resposta criada",
"survey_viewed": "Inquérito visualizado",
"survey_viewed_at": "Visualizado em",
"system_attributes": "Atributos do sistema",
@@ -1891,6 +1891,7 @@
"completed": "Concluído ✅",
"country": "País",
"decrement_quotas": "Decrementar todos os limites das cotas incluindo esta resposta",
"delete_response": "Eliminar resposta",
"delete_response_confirmation": "Isto irá apagar a resposta do inquérito, incluindo todas as respostas, etiquetas, documentos anexos e metadados da resposta.",
"delete_response_quotas": "A resposta faz parte das quotas deste inquérito. Como deseja gerir as quotas?",
"device": "Dispositivo",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Pesquisar por nome do inquérito",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Cria um ID de utilização única legível e copia uma ligação assinada para o mesmo.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Usa um ID de utilização única personalizado no URL.",
"custom_start_point": "Ponto de início personalizado",
"data_prefilling": "Pré-preenchimento de dados",
"description": "Respostas provenientes destes links serão anónimas",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 zi rămasă în perioada ta de probă",
"try_again": "Încearcă din nou",
"type": "Tip",
"unknown_survey": "Chestionar necunoscut",
"unlock_more_workspaces_with_a_higher_plan": "Deblochează mai multe workspaces cu un plan superior.",
"update": "Actualizare",
"updated": "Actualizat",
@@ -716,6 +715,7 @@
"select_a_survey": "Selectați un sondaj",
"select_attribute": "Selectează atributul",
"select_attribute_key": "Selectează cheia atributului",
"survey_response_created": "Răspuns creat",
"survey_viewed": "Chestionar vizualizat",
"survey_viewed_at": "Vizualizat la",
"system_attributes": "Atribute de sistem",
@@ -1891,6 +1891,7 @@
"completed": "Finalizat ✅",
"country": "Țară",
"decrement_quotas": "Decrementați toate limitele cotelor, inclusiv acest răspuns",
"delete_response": "Șterge răspunsul",
"delete_response_confirmation": "Aceasta va șterge răspunsul la sondaj, inclusiv toate răspunsurile, etichetele, documentele atașate și metadatele răspunsului.",
"delete_response_quotas": "Răspunsul face parte din cotele pentru acest sondaj. Cum doriți să gestionați cotele?",
"device": "Dispozitiv",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Căutare după nume chestionar",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Creează un ID de unică folosință lizibil și copiază un link semnat pentru acesta.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Folosește un ID personalizat de unică folosință în URL.",
"custom_start_point": "Punct de start personalizat",
"data_prefilling": "Precompletare date",
"description": "Răspunsurile provenite de la aceste linkuri vor fi anonime",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "Остался 1 день пробного периода",
"try_again": "Попробуйте ещё раз",
"type": "Тип",
"unknown_survey": "Неизвестный опрос",
"unlock_more_workspaces_with_a_higher_plan": "Откройте больше рабочих пространств с более высоким тарифом.",
"update": "Обновить",
"updated": "Обновлено",
@@ -716,6 +715,7 @@
"select_a_survey": "Выберите опрос",
"select_attribute": "Выберите атрибут",
"select_attribute_key": "Выберите ключ атрибута",
"survey_response_created": "Ответ создан",
"survey_viewed": "Опрос просмотрен",
"survey_viewed_at": "Просмотрено",
"system_attributes": "Системные атрибуты",
@@ -1891,6 +1891,7 @@
"completed": "Завершено ✅",
"country": "Страна",
"decrement_quotas": "Уменьшить все лимиты квот, включая этот ответ",
"delete_response": "Удалить ответ",
"delete_response_confirmation": "Это действие удалит ответ на опрос, включая все ответы, теги, вложенные документы и метаданные ответа.",
"delete_response_quotas": "Ответ входит в квоты для этого опроса. Как вы хотите обработать квоты?",
"device": "Устройство",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Поиск по названию опроса",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Создайте читаемый одноразовый ID и скопируйте подписанную ссылку для него.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Используй собственный одноразовый ID в URL.",
"custom_start_point": "Пользовательская точка старта",
"data_prefilling": "Предзаполнение данных",
"description": "Ответы, полученные по этим ссылкам, будут анонимными",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "1 dag kvar av din provperiod",
"try_again": "Försök igen",
"type": "Typ",
"unknown_survey": "Okänd enkät",
"unlock_more_workspaces_with_a_higher_plan": "Lås upp fler arbetsytor med ett högre abonnemang.",
"update": "Uppdatera",
"updated": "Uppdaterad",
@@ -716,6 +715,7 @@
"select_a_survey": "Välj en enkät",
"select_attribute": "Välj attribut",
"select_attribute_key": "Välj attributnyckel",
"survey_response_created": "Svar skapat",
"survey_viewed": "Enkät visad",
"survey_viewed_at": "Visad kl.",
"system_attributes": "Systemattribut",
@@ -1891,6 +1891,7 @@
"completed": "Slutförd ✅",
"country": "Land",
"decrement_quotas": "Minska alla gränser för kvoter inklusive detta svar",
"delete_response": "Ta bort svar",
"delete_response_confirmation": "Detta kommer att ta bort enkätsvaret, inklusive alla svar, taggar, bifogade dokument och svarsmetadata.",
"delete_response_quotas": "Svaret är del av kvoter för denna enkät. Hur vill du hantera kvoterna?",
"device": "Enhet",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Sök efter enkätnamn",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Skapa ett läsbart engångs-ID och kopiera en signerad länk för det.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "Använd ett anpassat engångs-ID i URL:en.",
"custom_start_point": "Anpassad startpunkt",
"data_prefilling": "Dataförfyllning",
"description": "Svar från dessa länkar kommer att vara anonyma",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "Deneme sürenizde 1 gün kaldı",
"try_again": "Tekrar dene",
"type": "Tür",
"unknown_survey": "Bilinmeyen anket",
"unlock_more_workspaces_with_a_higher_plan": "Daha yüksek bir planla daha fazla çalışma alanının kilidini açın.",
"update": "Güncelle",
"updated": "Güncellendi",
@@ -716,6 +715,7 @@
"select_a_survey": "Bir survey seçin",
"select_attribute": "Özellik Seçin",
"select_attribute_key": "Özellik anahtarı seçin",
"survey_response_created": "Yanıt oluşturuldu",
"survey_viewed": "Survey görüntülendi",
"survey_viewed_at": "Görüntülenme Tarihi",
"system_attributes": "Sistem Özellikleri",
@@ -1891,6 +1891,7 @@
"completed": "Tamamlandı ✅",
"country": "Ülke",
"decrement_quotas": "Bu yanıtı içeren tüm kota limitlerini azalt",
"delete_response": "Yanıtı sil",
"delete_response_confirmation": "Bu işlem, tüm yanıtlar, etiketler, ekli belgeler ve yanıt meta verileri dahil olmak üzere survey yanıtını silecek.",
"delete_response_quotas": "Yanıt bu survey'in kotalarına dahil. Kotaları nasıl yönetmek istiyorsunuz?",
"device": "Cihaz",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "Survey adına göre ara",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "Okunabilir bir tek kullanımlık kimlik oluşturun ve bunun için imzalı bir bağlantı kopyalayın.",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "URL'de özel bir tek kullanımlık kimlik kullanın.",
"custom_start_point": "Özel başlangıç noktası",
"data_prefilling": "Veri ön doldurma",
"description": "Bu bağlantılardan gelen yanıtlar anonim olacaktır",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "试用期还剩 1 天",
"try_again": "再试一次",
"type": "类型",
"unknown_survey": "未知调查",
"unlock_more_workspaces_with_a_higher_plan": "升级套餐以解锁更多工作区。",
"update": "更新",
"updated": "已更新",
@@ -716,6 +715,7 @@
"select_a_survey": "选择一个调查",
"select_attribute": "选择 属性",
"select_attribute_key": "选择属性键",
"survey_response_created": "回复已创建",
"survey_viewed": "已查看调查",
"survey_viewed_at": "查看时间",
"system_attributes": "系统属性",
@@ -1891,6 +1891,7 @@
"completed": "完成 ✅",
"country": "国家",
"decrement_quotas": "减少所有配额限制,包括此回应",
"delete_response": "删除回复",
"delete_response_confirmation": "这 将 删除 调查 回应, 包括 所有 答案、 标签、 附件文档 和 回应元数据。",
"delete_response_quotas": "该响应是 此 调查配额 的一部分。 您 希望 如何 处理 这些 配额?",
"device": "设备",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "按 调查 名称 搜索",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "创建一个可读的一次性 ID,并复制其签名链接。",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "在 URL 中使用自定义一次性 ID。",
"custom_start_point": "自定义 起点",
"data_prefilling": "数据 预填充",
"description": "来自 这些 link 的 响应 将是 匿名 的",
+4 -3
View File
@@ -462,7 +462,6 @@
"trial_one_day_remaining": "試用期剩餘 1 天",
"try_again": "再試一次",
"type": "類型",
"unknown_survey": "未知問卷",
"unlock_more_workspaces_with_a_higher_plan": "升級方案以解鎖更多工作區。",
"update": "更新",
"updated": "已更新",
@@ -716,6 +715,7 @@
"select_a_survey": "選擇問卷",
"select_attribute": "選取屬性",
"select_attribute_key": "選取屬性鍵值",
"survey_response_created": "回應已建立",
"survey_viewed": "已查看問卷",
"survey_viewed_at": "查看時間",
"system_attributes": "系統屬性",
@@ -1891,6 +1891,7 @@
"completed": "已完成 ✅",
"country": "國家/地區",
"decrement_quotas": "減少所有配額限制,包括此回應",
"delete_response": "刪除回應",
"delete_response_confirmation": "這將刪除調查響應,包括所有回答、標籤、附件文件以及響應元數據。",
"delete_response_quotas": "回應 屬於 此 調查 的 配額 一部分 . 你 想 如何 處理 配額?",
"device": "裝置",
@@ -1918,10 +1919,10 @@
"search_by_survey_name": "依問卷名稱搜尋",
"share": {
"anonymous_links": {
"custom_single_use_id_description": "Create a readable single-use ID and copy a signed link for it.",
"custom_single_use_id_description": "建立可讀的單次使用 ID,並複製其簽署連結。",
"custom_single_use_id_placeholder": "CUSTOM-ID",
"custom_single_use_id_required": "Enter a custom single-use ID.",
"custom_single_use_id_title": "Use a custom single-use ID in the URL.",
"custom_single_use_id_title": "在網址中使用自訂的單次使用 ID。",
"custom_start_point": "自訂 開始 點",
"data_prefilling": "資料預先填寫",
"description": "從 這些 連結 獲得 的 回應 將是 匿名 的",
@@ -9,6 +9,7 @@ import { TTag } from "@formbricks/types/tags";
import { TUserLocale } from "@formbricks/types/user";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { TagError } from "@/modules/projects/settings/types/tag";
import { IdBadge } from "@/modules/ui/components/id-badge";
import { Tag } from "@/modules/ui/components/tag";
import { TagsCombobox } from "@/modules/ui/components/tags-combobox";
import { createTagAction, createTagToResponseAction, deleteTagOnResponseAction } from "../actions";
@@ -160,6 +161,7 @@ export const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
/>
)}
</div>
<IdBadge id={responseId} />
</div>
);
};
@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils";
import { TResponse } from "@formbricks/types/responses";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@/modules/ui/components/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
interface InfoIconButtonProps {
@@ -25,11 +26,9 @@ const InfoIconButton = ({
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
className="flex h-4 w-4 items-center justify-center rounded text-slate-500 hover:text-slate-700"
aria-label={ariaLabel}>
<Button variant="outline" size="icon" aria-label={ariaLabel}>
<Icon className="h-4 w-4" />
</button>
</Button>
</TooltipTrigger>
<TooltipContent avoidCollisions align="start" side="bottom" className={maxWidth}>
{tooltipContent}
@@ -1,6 +1,6 @@
"use client";
import { useMemo, useState } from "react";
import { ReactNode, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
@@ -16,7 +16,12 @@ import { deleteResponseAction, getResponseAction } from "./actions";
import { ResponseTagsWrapper } from "./components/ResponseTagsWrapper";
import { SingleResponseCardBody } from "./components/SingleResponseCardBody";
import { SingleResponseCardHeader } from "./components/SingleResponseCardHeader";
import { isValidValue } from "./util";
import { isSubmissionTimeMoreThan5Minutes, isValidValue } from "./util";
export interface SingleResponseCardHeaderRenderProps {
onDeleteClick: () => void;
canResponseBeDeleted: boolean;
}
interface SingleResponseCardProps {
survey: TSurvey;
@@ -29,6 +34,11 @@ interface SingleResponseCardProps {
isReadOnly: boolean;
setSelectedResponseId?: (responseId: string | null) => void;
locale: TUserLocale;
/**
* Optional render-prop to replace the default header. Receives helpers to
* trigger the (shared) delete dialog so callers don't have to reimplement it.
*/
renderHeader?: (props: SingleResponseCardHeaderRenderProps) => ReactNode;
}
export const SingleResponseCard = ({
@@ -42,7 +52,8 @@ export const SingleResponseCard = ({
isReadOnly,
setSelectedResponseId,
locale,
}: SingleResponseCardProps) => {
renderHeader,
}: Readonly<SingleResponseCardProps>) => {
const hasQuotas = (response?.quotas && response.quotas.length > 0) ?? false;
const [decrementQuotas, setDecrementQuotas] = useState(hasQuotas);
const { t } = useTranslation();
@@ -128,19 +139,30 @@ export const SingleResponseCard = ({
}
};
const canResponseBeDeleted = response.finished
? true
: isSubmissionTimeMoreThan5Minutes(response.updatedAt);
return (
<div className="group relative">
<div className="relative z-20 my-6 rounded-xl border border-slate-200 bg-white shadow-sm transition-all">
<SingleResponseCardHeader
pageType="response"
response={response}
survey={survey}
environment={environment}
user={user}
isReadOnly={isReadOnly}
setDeleteDialogOpen={setDeleteDialogOpen}
locale={locale}
/>
<div className="relative z-20 rounded-xl border border-slate-200 bg-white shadow-sm transition-all">
{renderHeader ? (
renderHeader({
onDeleteClick: () => setDeleteDialogOpen(true),
canResponseBeDeleted,
})
) : (
<SingleResponseCardHeader
pageType="response"
response={response}
survey={survey}
environment={environment}
user={user}
isReadOnly={isReadOnly}
setDeleteDialogOpen={setDeleteDialogOpen}
locale={locale}
/>
)}
<SingleResponseCardBody
survey={survey}
@@ -18,8 +18,8 @@ import { DisplayCard } from "./display-card";
import { ResponseSurveyCard } from "./response-survey-card";
type TTimelineItem =
| { type: "display"; data: Pick<TDisplay, "id" | "createdAt" | "surveyId"> }
| { type: "response"; data: TResponseWithQuotas };
| { type: "display"; data: Pick<TDisplay, "id" | "createdAt" | "surveyId">; survey: TSurvey }
| { type: "response"; data: TResponseWithQuotas; survey: TSurvey };
interface ActivityTimelineProps {
surveys: TSurvey[];
@@ -41,7 +41,7 @@ export const ActivityTimeline = ({
environmentTags,
locale,
projectPermission,
}: ActivityTimelineProps) => {
}: Readonly<ActivityTimelineProps>) => {
const { t } = useTranslation();
const [responses, setResponses] = useState(initialResponses);
const [isReversed, setIsReversed] = useState(false);
@@ -66,16 +66,20 @@ export const ActivityTimeline = ({
setResponses((prev) => prev.map((r) => (r.id === responseId ? updatedResponse : r)));
};
const timelineItems = useMemo(() => {
const displayItems: TTimelineItem[] = displays.map((d) => ({
type: "display" as const,
data: d,
}));
const surveyById = useMemo(() => {
return new Map(surveys.map((s) => [s.id, s]));
}, [surveys]);
const responseItems: TTimelineItem[] = responses.map((r) => ({
type: "response" as const,
data: r,
}));
const timelineItems = useMemo(() => {
const displayItems: TTimelineItem[] = displays.flatMap((d) => {
const survey = surveyById.get(d.surveyId);
return survey ? [{ type: "display" as const, data: d, survey }] : [];
});
const responseItems: TTimelineItem[] = responses.flatMap((r) => {
const survey = surveyById.get(r.surveyId);
return survey ? [{ type: "response" as const, data: r, survey }] : [];
});
const merged = [...displayItems, ...responseItems].sort((a, b) => {
const aTime = new Date(a.data.createdAt).getTime();
@@ -84,7 +88,7 @@ export const ActivityTimeline = ({
});
return isReversed ? [...merged].reverse() : merged;
}, [displays, responses, isReversed]);
}, [displays, responses, surveyById, isReversed]);
const toggleSort = () => {
setIsReversed((prev) => !prev);
@@ -112,7 +116,7 @@ export const ActivityTimeline = ({
<DisplayCard
key={`display-${item.data.id}`}
display={item.data}
surveys={surveys}
survey={item.survey}
environmentId={environment.id}
locale={locale}
/>
@@ -120,7 +124,7 @@ export const ActivityTimeline = ({
<ResponseSurveyCard
key={`response-${item.data.id}`}
response={item.data}
surveys={surveys}
survey={item.survey}
user={user}
environmentTags={environmentTags}
environment={environment}
@@ -1,8 +1,8 @@
"use client";
import { LinkIcon, PencilIcon, TrashIcon } from "lucide-react";
import { LinkIcon, PencilIcon, RefreshCwIcon, TrashIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useState, useTransition } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TContactAttributeDataType, TContactAttributeKey } from "@formbricks/types/contact-attribute-key";
@@ -46,6 +46,13 @@ export const ContactControlBar = ({
const [isDeletingPerson, setIsDeletingPerson] = useState(false);
const [isGenerateLinkModalOpen, setIsGenerateLinkModalOpen] = useState(false);
const [isEditAttributesModalOpen, setIsEditAttributesModalOpen] = useState(false);
const [isRefreshing, startRefreshTransition] = useTransition();
const handleRefresh = () => {
startRefreshTransition(() => {
router.refresh();
});
};
const handleDeletePerson = async () => {
setIsDeletingPerson(true);
@@ -62,18 +69,22 @@ export const ContactControlBar = ({
setDeleteDialogOpen(false);
};
if (isReadOnly) {
return null;
}
const iconActions = [
{
icon: RefreshCwIcon,
tooltip: t("common.refresh"),
onClick: handleRefresh,
isVisible: true,
disabled: isRefreshing,
iconClassName: isRefreshing ? "animate-spin" : undefined,
},
{
icon: PencilIcon,
tooltip: t("environments.contacts.edit_attributes"),
onClick: () => {
setIsEditAttributesModalOpen(true);
},
isVisible: true,
isVisible: !isReadOnly,
},
{
icon: LinkIcon,
@@ -81,7 +92,7 @@ export const ContactControlBar = ({
onClick: () => {
setIsGenerateLinkModalOpen(true);
},
isVisible: true,
isVisible: !isReadOnly,
},
{
icon: TrashIcon,
@@ -89,7 +100,7 @@ export const ContactControlBar = ({
onClick: () => {
setDeleteDialogOpen(true);
},
isVisible: true,
isVisible: !isReadOnly,
},
];
@@ -10,14 +10,13 @@ import { timeSince } from "@/lib/time";
interface DisplayCardProps {
display: Pick<TDisplay, "id" | "createdAt" | "surveyId">;
surveys: TSurvey[];
survey: TSurvey;
environmentId: string;
locale: TUserLocale;
}
export const DisplayCard = ({ display, surveys, environmentId, locale }: DisplayCardProps) => {
export const DisplayCard = ({ display, survey, environmentId, locale }: Readonly<DisplayCardProps>) => {
const { t } = useTranslation();
const survey = surveys.find((s) => s.id === display.surveyId);
return (
<div className="flex items-center justify-between rounded-xl border border-slate-200 bg-white p-4 shadow-sm">
@@ -27,15 +26,11 @@ export const DisplayCard = ({ display, surveys, environmentId, locale }: Display
</div>
<div>
<p className="text-xs text-slate-500">{t("environments.contacts.survey_viewed")}</p>
{survey ? (
<Link
href={`/environments/${environmentId}/surveys/${survey.id}/summary`}
className="text-sm font-medium text-slate-700 hover:underline">
{survey.name}
</Link>
) : (
<span className="text-sm font-medium text-slate-500">{t("common.unknown_survey")}</span>
)}
<Link
href={`/environments/${environmentId}/surveys/${survey.id}/summary`}
className="text-sm font-medium text-slate-700 hover:underline">
{survey.name}
</Link>
</div>
</div>
<span className="text-sm text-slate-500">{timeSince(display.createdAt.toString(), locale)}</span>
@@ -1,16 +1,23 @@
"use client";
import { MessageSquareTextIcon, TrashIcon } from "lucide-react";
import Link from "next/link";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { TEnvironment } from "@formbricks/types/environment";
import { TResponseWithQuotas } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
import { TUser, TUserLocale } from "@formbricks/types/user";
import { timeSince } from "@/lib/time";
import { replaceHeadlineRecall } from "@/lib/utils/recall";
import { SingleResponseCard } from "@/modules/analysis/components/SingleResponseCard";
import { Button } from "@/modules/ui/components/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
interface ResponseSurveyCardProps {
response: TResponseWithQuotas;
surveys: TSurvey[];
survey: TSurvey;
user: TUser;
environmentTags: TTag[];
environment: TEnvironment;
@@ -22,7 +29,7 @@ interface ResponseSurveyCardProps {
export const ResponseSurveyCard = ({
response,
surveys,
survey,
user,
environmentTags,
environment,
@@ -30,22 +37,74 @@ export const ResponseSurveyCard = ({
updateResponse,
locale,
isReadOnly,
}: ResponseSurveyCardProps) => {
const survey = surveys.find((s) => s.id === response.surveyId);
}: Readonly<ResponseSurveyCardProps>) => {
const { t } = useTranslation();
if (!survey) return null;
const surveyWithReplacedRecall = useMemo(() => replaceHeadlineRecall(survey, "default"), [survey]);
const showDeleteButton = !!user && !isReadOnly;
return (
<SingleResponseCard
survey={surveyWithReplacedRecall}
response={response}
survey={replaceHeadlineRecall(survey, "default")}
user={user}
environmentTags={environmentTags}
environment={environment}
updateResponseList={updateResponseList}
updateResponse={updateResponse}
environmentTags={environmentTags}
isReadOnly={isReadOnly}
updateResponse={updateResponse}
updateResponseList={updateResponseList}
locale={locale}
renderHeader={({ onDeleteClick, canResponseBeDeleted }) => (
<div className="flex items-center justify-between p-4">
<div className="flex min-w-0 items-center gap-3">
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-slate-100">
<MessageSquareTextIcon className="h-4 w-4 text-slate-600" />
</div>
<div className="min-w-0">
<p className="text-xs text-slate-500">{t("environments.contacts.survey_response_created")}</p>
<Link
href={`/environments/${environment.id}/surveys/${survey.id}/summary`}
className="block truncate text-sm font-medium text-slate-700 hover:underline">
{survey.name}
</Link>
</div>
</div>
<div className="flex items-center gap-1 text-sm text-slate-500">
<time className="px-1" dateTime={response.createdAt.toString()}>
{timeSince(response.createdAt.toString(), locale)}
</time>
{showDeleteButton &&
(canResponseBeDeleted ? (
<Button
variant="ghost"
size="icon"
onClick={onDeleteClick}
aria-label={t("environments.surveys.responses.delete_response")}>
<TrashIcon className="h-4 w-4" />
</Button>
) : (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
disabled
className="text-slate-400"
aria-label={t("environments.surveys.responses.delete_response")}>
<TrashIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="left">
{t("environments.surveys.responses.this_response_is_in_progress")}
</TooltipContent>
</Tooltip>
</TooltipProvider>
))}
</div>
</div>
)}
/>
);
};
@@ -8,6 +8,7 @@ interface IconAction {
onClick?: () => void;
isVisible?: boolean;
disabled?: boolean;
iconClassName?: string;
}
interface IconBarProps {
@@ -16,30 +17,30 @@ interface IconBarProps {
}
export const IconBar = ({ actions }: IconBarProps) => {
if (actions.length === 0) return null;
const visibleActions = actions.filter((action) => action.isVisible);
if (visibleActions.length === 0) return null;
return (
<div
className="flex items-center justify-center divide-x rounded-md border border-slate-300 bg-white"
role="toolbar"
aria-label="Action buttons">
{actions
.filter((action) => action.isVisible)
.map((action, index) => (
<span key={`${action.tooltip}-${index}`}>
<TooltipRenderer tooltipContent={action.tooltip}>
<Button
variant="ghost"
className="border-none hover:bg-slate-50"
size="icon"
onClick={action.onClick}
disabled={action.disabled}
aria-label={action.tooltip}>
<action.icon />
</Button>
</TooltipRenderer>
</span>
))}
{visibleActions.map((action, index) => (
<span key={`${action.tooltip}-${index}`}>
<TooltipRenderer tooltipContent={action.tooltip}>
<Button
variant="ghost"
className="border-none hover:bg-slate-50"
size="icon"
onClick={action.onClick}
disabled={action.disabled}
aria-label={action.tooltip}>
<action.icon className={action.iconClassName} />
</Button>
</TooltipRenderer>
</span>
))}
</div>
);
};