Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .updateTdev
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CURRENT_COMMIT=2c82396f402c775a173e7abb76b946d892455e77
CURRENT_COMMIT=a136bb8241d5d354af1a462fb2cd3fc8ebb337b6
1 change: 1 addition & 0 deletions docs/01-infrastructure/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_id: 550b5b44-9742-4b32-9991-c9c7a77a57ae
hide_rating: true
sidebar_custom_props:
icon: mdi-home-circle
sidebar_class_name: hidden-item
Expand Down
1 change: 1 addition & 0 deletions docs/02-software-communication/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_id: 3804f04d-9025-45da-bd78-2499f3bd6e1e
hide_rating: true
sidebar_custom_props:
icon: mdi-cellphone-link
---
Expand Down
1 change: 1 addition & 0 deletions docs/04-accounts/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_id: a30b22f8-09f9-4644-93fe-1ed8dfd0136f
hide_rating: true
sidebar_custom_props:
icon: mdi-account-circle-outline
---
Expand Down
1 change: 1 addition & 0 deletions docs/05-tips/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_id: 6d927439-23fd-4de0-8c55-021d2dd78452
hide_rating: true
sidebar_custom_props:
icon: mdi-lightbulb-on-outline
---
Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
"dependencies": {
"@azure/msal-browser": "^v3.28.0",
"@azure/msal-react": "^2.2.0",
"@docusaurus/core": "^3.10.0",
"@docusaurus/faster": "^3.10.0",
"@docusaurus/plugin-client-redirects": "^3.10.0",
"@docusaurus/preset-classic": "^3.10.0",
"@docusaurus/theme-classic": "^3.10.0",
"@docusaurus/theme-common": "^3.10.0",
"@docusaurus/theme-mermaid": "^3.10.0",
"@docusaurus/core": "^3.10.1",
"@docusaurus/faster": "^3.10.1",
"@docusaurus/plugin-client-redirects": "^3.10.1",
"@docusaurus/preset-classic": "^3.10.1",
"@docusaurus/theme-classic": "^3.10.1",
"@docusaurus/theme-common": "^3.10.1",
"@docusaurus/theme-mermaid": "^3.10.1",
"@lexical/clipboard": "^0.35.0",
"@lexical/link": "^0.35.0",
"@lexical/list": "^0.35.0",
Expand Down Expand Up @@ -96,10 +96,10 @@
"uuid": "^13.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.0",
"@docusaurus/plugin-rsdoctor": "^3.10.0",
"@docusaurus/tsconfig": "^3.10.0",
"@docusaurus/types": "^3.10.0",
"@docusaurus/module-type-aliases": "^3.10.1",
"@docusaurus/plugin-rsdoctor": "^3.10.1",
"@docusaurus/tsconfig": "^3.10.1",
"@docusaurus/types": "^3.10.1",
"@types/exceljs": "^1.3.2",
"@types/fs-extra": "^11.0.4",
"@types/js-yaml": "^4.0.9",
Expand Down
4 changes: 4 additions & 0 deletions packages/ict/_category_.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
label: '@ict/'
link:
type: generated-index
title: '@ict/ Packages'
145 changes: 145 additions & 0 deletions packages/ict/directus/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
page_id: 3da35657-6126-4ab7-9689-9dce8b2d31fa
---

# Directus Page Ratings

Mit `@ict/directus` werden simple Page Ratings am Ende jeder Seite dargestellt.

![](./images/page-feedback.png)

## Frontmatter

Über das Frontmatter können zusätzliche Einstellungen vorgenommen werden:

`hide_rating`
: `boolean` - Versteckt die Page Ratings auf der Seite, auf der diese Einstellung gesetzt ist. Nützlich für Seiten, die nicht bewertet werden sollen (z.B. Startseite, Kontaktseite, etc.).
: @default `false`
`rating_label`
: `string` - Überschreibt die Standard-Beschriftung.
: @default `Rückmeldung zu dieser Seite?`

:::details[Zusammenfassung anzeigen]
Um die Zusammenfassung (durchschnittliche Bewertung und Anzahl der Bewertungen) anzuzeigen,
kann das Label 5x geklickt werden.
:::

## Directus Aufsetzen

### Directus Collection
Erstelle eine Collection (`ict_page_ratings`) mit folgenden Feldern:
- `id` (integer, autoincrement, primary key)
- `rating` (integer)
- `client_id` (string)
- `page_id` (string)
- `pathname` (string)
- `created_at` (datetime, autogeneriert)

### Directus Flow

Erstelle einen Flow um mehrfache Bewertungen vom selben Client zu bereinigen. Dies könnte auch durch DB-Constraints und `update` statt `create` zu verwenden, was mehr Client-Code erfordern würde.

![](./images/feedback-unifier-flow.png)

Trigger
: `items.create` Trigger auf `ict_page_ratings`
: `Non-Blocking`
Delete Data
: Collection `ict_page_ratings`
: Permission `full access`
: Query
:::dd
```json
{
"filter": {
"id": {
"_neq": "{{$trigger.key}}"
},
"page_id": {
"_eq": "{{$trigger.payload.page_id}}"
},
"client_id": {
"_eq": "{{$trigger.payload.client_id}}"
}
}
}
```
:::

### CORS
Sicherstellen, dass die Directus-Instanz CORS-Anfragen von der Domain (ict-Seite) erlaubt.

```env title="/home/dokku/directus/ENV"
CORS_ENABLED="true"
CORS_ORIGIN="https://ict.gbsl.website"
```

### Access Policies

Damit ein nicht angemeldete Nutzer:in eine Bewertung abgeben kann, muss unter __Settings > Access Policies > Public Policy > #Permissions__ folgende Einstellung vorgenommen werden:

Collection
: `ict_page_ratings`
Actions
: `Create`
: `Read`


## Installation

1. `packages/ict/directus` kopieren und `yarn install` ausführen.
2. In der `siteConfig` die Directus-Konfiguration hinzufügen:
```ts title="siteConfig.ts"
import { type DirectusConfig } from '@ict/directus';

declare module './src/siteConfig/siteConfig' {
export interface TdevConfig {
directus: DirectusConfig;
}
}

const getSiteConfig: SiteConfigProvider = () => {
return {
/*...*/
tdevConfig: {
directus: {
collection: 'page_ratings',
url: 'https://directus.foo.ch'
}
},
apiDocumentProviders: [
require.resolve('@ict/directus/register')
],
}
}
```
3. `DocItem.Footer` swizzeln (`wrap`):
```tsx title="src/theme/DocItem/Footer/index.tsx"
import Footer from '@theme-original/DocItem/Footer';
import PageRating from '@ict/directus/components/PageRating';
import PageSummary from '@ict/directus/components/PageSummary';
import { useDoc } from '@docusaurus/plugin-content-docs/client';

type Props = WrapperProps<typeof FooterType>;

type DocFrontMatter = {
page_id: string;
rating_label?: string;
hide_rating?: boolean;
};
const FooterWrapper = (props: Props): ReactNode => {
const { frontMatter } = useDoc();
const {
page_id: pageId,
rating_label: ratingLabel,
hide_rating: hideRating
} = frontMatter as DocFrontMatter;
return (
<div>
<PageRating pageId={pageId} label={ratingLabel} />
<PageSummary pageId={pageId} />
<Footer {...props} />
</div>
)
};
```
80 changes: 80 additions & 0 deletions packages/ict/directus/components/PageRating/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.scss';
import { useStore } from '@tdev-hooks/useStore';
import { observer } from 'mobx-react-lite';
import { mdiStar, mdiStarOutline, mdiLoading } from '@mdi/js';
import Button from '@tdev-components/shared/Button';
import { useLocation } from '@docusaurus/router';

interface Props {
pageId: string;
label?: string;
}

const getIconPath = (starNr: number, rating?: number) => {
if (!rating) {
return mdiStarOutline;
}
if (rating < 0 && starNr === Math.abs(rating)) {
return mdiLoading;
}
if (starNr <= Math.abs(rating)) {
return mdiStar;
}
return mdiStarOutline;
};

const PageRating = observer((props: Props) => {
const { pageId } = props;
const clientStore = useStore('viewStore');
const { pathname } = useLocation();
const directusStore = clientStore.useStore('directusStore');
const rating = directusStore.pageRatings.get(pageId) ?? 0;
const [clickedAt, setClickedAt] = React.useState<number[]>([]);
React.useEffect(() => {
if (!pageId) {
return;
}
directusStore.fetchPageRating(pageId);
}, [pageId, directusStore]);

React.useEffect(() => {
const now = Date.now();
const withinLastSecond = clickedAt.filter((time) => now - time < 1000);
if (withinLastSecond.length < 5) {
return;
}
directusStore.setShowSummary(true);
setClickedAt([]);
}, [clickedAt]);

return (
<div className={clsx(styles.pageSummary)}>
<span
onClick={() => setClickedAt((prev) => [...prev, Date.now()])}
className={clsx(styles.label)}
>
{props.label || 'Rückmeldung zu dieser Seite?'}
</span>
<div className={clsx(styles.rating)}>
{[1, 2, 3, 4, 5].map((i) => {
const isActive = i <= Math.abs(rating);
return (
<Button
icon={getIconPath(i, rating)}
color={isActive ? 'gold' : 'var(--ifm-color-secondary-darkest)'}
key={i}
spin={rating === -i}
onClick={() => {
directusStore.submitRating(pageId, pathname, i);
}}
/>
);
})}
</div>
</div>
);
});

export default PageRating;
11 changes: 11 additions & 0 deletions packages/ict/directus/components/PageRating/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.pageSummary {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
.rating {
display: flex;
}
.label {
user-select: none;
}
}
56 changes: 56 additions & 0 deletions packages/ict/directus/components/PageSummary/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.scss';
import { useStore } from '@tdev-hooks/useStore';
import { observer } from 'mobx-react-lite';
import { mdiStar, mdiStarHalfFull, mdiStarOutline } from '@mdi/js';
import Badge from '@tdev-components/shared/Badge';
import Icon from '@mdi/react';

interface Props {
pageId: string;
}

const getIconPath = (avg?: number) => {
if (!avg || avg < 2) {
return mdiStarOutline;
}
if (avg > 3) {
return mdiStar;
}
return mdiStarHalfFull;
};

const PageSummary = observer((props: Props) => {
const { pageId } = props;
const clientStore = useStore('viewStore');
const directusStore = clientStore.useStore('directusStore');
React.useEffect(() => {
if (!pageId || !directusStore.showSummary) {
return;
}
directusStore.fetchPageRatings(pageId);
}, [pageId, directusStore.showSummary]);

if (!directusStore.showSummary) {
return null;
}

const summary = directusStore.pageSummary.get(pageId);

return (
<div className={clsx(styles.pageSummary)}>
Zusammenfassung{' '}
{directusStore.showSummary && (
<Badge
title={`${summary?.count ?? 0} Bewertungen, durchschnittlich ${summary?.avg?.toFixed(1) ?? 'null'} Sterne`}
>
{summary?.avg?.toFixed(1) ?? 'null'}{' '}
<Icon path={getIconPath(summary?.avg)} size={0.8} color="gold" /> {summary?.count ?? 0}
</Badge>
)}
</div>
);
});

export default PageSummary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.pageSummary {
float: right;
}
Loading
Loading