Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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 packages/client/src/ts/session/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export type School = {
id: string; // "09772a06-1362-4802-a475-66a87d9cb679"
name: string; // "MY DEV SCHOOL"
UAI: string; // "1111888G"
exports: string[]; // ["GAR-P0"]
exports: string[] | null; // ["GAR-P0"]
};
export type UserProfile = Array<
'Student' | 'Teacher' | 'Relative' | 'Personnel' | 'Guest'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
:root {
--school-widget-border-color: #ffe5a3;
--school-widget-bg-color: #fefaec;
--school-widget-selected-color: #383838;
}

.school-widget {
border: 1px solid var(--school-widget-border-color);
border-radius: 2.4rem;

background-color: var(--school-widget-bg-color);
background-image: url('./SchoolWidgetBackground.svg');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est une image qui va être lié aux préférénces de couleur qui seront accessibles dans le panneau de configuration. Pas en v1 mais faudra y penser.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. bien vu, mais c'est un comportement qu'on va devoir mettre en place à plusieurs endroit. Tu as une idée de comment faire ? on peut passer des proriétés à un svg ?

background-repeat: no-repeat;
background-position: bottom right;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { Meta, StoryObj } from '@storybook/react';
import SchoolWidget, { SchoolWidgetProps } from './SchoolWidget';

const meta: Meta<typeof SchoolWidget> = {
title: 'Modules/Homepage/SchoolWidget',
component: SchoolWidget,
decorators: [
(Story) => (
<div style={{ height: '35em' }}>
<div id="portal" />
<Story />
</div>
),
],
parameters: {
docs: {
description: {
component:
"Ce storybook documente le composant SchoolWidget, un widget de sélection d'école avec plusieurs variantes possibles.",
},
},
},
};

export default meta;
type Story = StoryObj<typeof SchoolWidget>;

const renderWithProps = (props: SchoolWidgetProps) => () => (
<div style={{ maxWidth: 397 }}>
<SchoolWidget {...props} />
</div>
);

const schools = [
{
id: 'school-1',
name: 'Collège Jean Moulin',
UAI: '0012345A',
classes: [],
exports: [],
},
{
id: 'school-2',
name: 'Lycée Jeanne Ferry de Loisette en Royan',
UAI: '0098765Z',
classes: [],
exports: [],
},
];

export const MultipleSchools: Story = {
render: renderWithProps({
schools,
selectedSchool: {
id: 'school-2',
name: 'Lycée Jeanne Ferry de Loisette en Royan',
UAI: '0098765Z',
classes: [],
exports: [],
},
onSelectedSchoolChange: (idx) =>
alert(
`School id=${schools[idx].id} UAI=${schools[idx].UAI} is selected.`,
),
}),
parameters: {
docs: {
description: {
story: `
Affiche une liste de plusieurs écoles avec sélection active.
<ul>
<li>2 écoles disponibles (Collège Jean Moulin, Lycée Jeanne Ferry de Loisette en Royan)</li>
<li>École sélectionnée : Lycée Jeanne Ferry</li>
<li>Callback au changement de sélection</li>
</ul>`,
},
},
},
};

export const SingleSchool: Story = {
render: renderWithProps({
schools: [schools[0]],
selectedSchool: schools[0],
}),
parameters: {
docs: {
description: {
story: `Affiche une seule école`,
},
},
},
};

export const Empty: Story = {
render: renderWithProps({ selectedSchool: undefined }),
parameters: {
docs: {
description: {
story: `État vide (aucune école)`,
},
},
},
};
Comment on lines +95 to +104
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est vraiment utile ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'aime bien avoir la possibilité de voir les Edge Case dans storybook, ça retire pas mal de questionnements quand on regarde le composant

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

est ce que le cas avec aucune ecole est possible ?

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { School } from '@edifice.io/client';
import { Dropdown, Flex, IconButton, useToggle } from '@edifice.io/react';
import { IconRafterDown } from '@edifice.io/react/icons';
import { RefAttributes } from 'react';
import { useTranslation } from 'react-i18next';
import './SchoolWidget.css';

export interface SchoolWidgetProps {
selectedSchool: School | undefined;
onSelectedSchoolChange?: (schoolIndex: number) => void;
schools?: School[];
}

const SchoolWidget = ({
schools,
selectedSchool,
onSelectedSchoolChange,
}: SchoolWidgetProps) => {
const [isExpanded, toggleExpanded] = useToggle(false);
const { t } = useTranslation();

const hasManySchools = schools && schools.length > 1;

const widgetStyle = { padding: '1.4rem 0.4rem' };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mettre le css en dehors du composant et au même endroit, dans le fichier .scss (cf commentaire precedent). C'est un point important à acter pour le rester du projet

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tout ce style doit être dans un fichier CSS ou SCSS.
Les seuls styles inlines qu'on va s'autoriser sont ceux qui sont calculés en fonction d'un state ou d'une variable du composant.

const containerStyle = { padding: '0.8rem' };
const selectedSchoolStyle = {
'padding': '.4rem 2.9rem',
'font-size': '1.6rem',
'line-height': '2.2rem',
'color': 'var(--school-widget-selected-color)',
};

if (!selectedSchool) return null;

return (
<div className="school-widget" style={widgetStyle}>
<div style={containerStyle}>
<Flex
style={selectedSchoolStyle}
justify="center"
gap="4"
align="center"
>
<b>{selectedSchool.name}</b>
{hasManySchools && (
<Dropdown placement={'bottom-end'} onToggle={toggleExpanded}>
{(triggerProps: RefAttributes<HTMLButtonElement>) => (
<>
<IconButton
{...triggerProps}
aria-label={t('show')}
color="tertiary"
variant="ghost"
icon={
<IconRafterDown
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wahou chiant d'avoir dû reimplementer ça. J'ai fait un commentaire à simon https://www.figma.com/design/UbAKNC5vk5XOj62z6CI4Qm?node-id=2146-36161#1690319223

className="w-16 min-w-0"
style={{
transition: 'rotate 0.2s ease-out',
rotate: isExpanded ? '0deg' : '-180deg',
}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si tu peux mettre un TODO pour récupérer ce que j'ai fait sur le composant de Partage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cad ? Tu peux mettre un lien sur le code en question

/>
}
/>
<Dropdown.Menu>
{schools.map((school, index) => (
<Dropdown.Item
key={school.id}
onClick={() => onSelectedSchoolChange?.(index)}
>
<Flex direction="column">
<p>{school.name}</p>
{school.UAI && <p>UAI : {school.UAI}</p>}
</Flex>
</Dropdown.Item>
))}
</Dropdown.Menu>
</>
)}
</Dropdown>
)}
</Flex>
</div>
</div>
);
};

SchoolWidget.displayName = 'SchoolWidget';

export default SchoolWidget;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { School, WIDGET_NAME, WidgetUserPref } from '@edifice.io/client';
import { useEffect, useState } from 'react';
import { useEdificeClient } from 'src/providers';
import useWidgetPreferences from '../../hooks/useWidgetPreferences';

export function useUserSchools() {
const { userDescription } = useEdificeClient();
const { lookup, saveUserPreferences } = useWidgetPreferences();
const [userPreferences, setUserPreferences] = useState<WidgetUserPref>();
const [selectedSchool, setSelectedSchool] = useState<School>();

const schools = userDescription?.schools;

useEffect(() => {
setSelectedSchool(schools?.[0]);
}, []);

useEffect(() => {
const userPref = lookup?.(WIDGET_NAME.SCHOOL)?.userPref;
setUserPreferences(userPref);
}, [lookup]);

useEffect(() => {
if (userPreferences?.schoolId && Array.isArray(schools)) {
const index = schools.findIndex(
(school) => school.id === userPreferences?.schoolId,
);
setSelectedSchool(
index < 0 || index >= schools.length ? schools[0] : schools[index],
);
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

est ce vraiment utile de gerer ce cas ? c'est possible fonctionnellement qu'il passe à undefined ?

setSelectedSchool(undefined);
}
}, [userPreferences]);

return {
schools: schools ?? [],
selectedSchool,
handleSelectedSchoolChange: (school: School) => {
setSelectedSchool(school);
if (userPreferences) {
userPreferences.schoolId = school.id;
saveUserPreferences();
}
},
};
}
22 changes: 22 additions & 0 deletions packages/react/src/modules/homepage/hooks/useWidgetPreferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IWidgetFramework, WidgetFrameworkFactory } from '@edifice.io/client';
import { useMutation } from '@tanstack/react-query';
import { useEffect, useState } from 'react';

export default function useWidgetPreferences() {
const [svc, setSvc] = useState<IWidgetFramework>();

const saveMutation = useMutation({
mutationFn: svc?.saveUserPrefs,
});

useEffect(() => {
const widgetService = WidgetFrameworkFactory.instance();
widgetService.initialize(null, null).then(() => setSvc(widgetService));
}, []);

return {
list: svc?.list,
lookup: svc?.lookup,
saveUserPreferences: saveMutation.mutateAsync,
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useEffect, useMemo } from 'react';

import { App } from '@edifice.io/client';
import { useTranslation } from 'react-i18next';
import { useConf } from '../../hooks/useConf';
import { useSession } from '../../hooks/useSession';
Expand All @@ -9,13 +8,6 @@ import {
EdificeClientProviderProps,
} from './EdificeClientProvider.context';

export interface OdeProviderParams {
alternativeApp?: boolean;
app: App;
cdnDomain?: string | null;
version?: string | null;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merci pour le nettoyage !

export function EdificeClientProvider({
children,
params,
Expand Down