Skip to content

Commit de6b0e6

Browse files
Merge pull request #327 from UniversityOfHelsinkiCS/add-teacher-to-chat-instance-manual
Add teacher to chat instance manual
2 parents 8060ef2 + 0c28d63 commit de6b0e6

File tree

11 files changed

+294
-42
lines changed

11 files changed

+294
-42
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"dev": "concurrently \"npm run dev:server\" \"vite\"",
1010
"dev:server": "NODE_ENV=development tsx watch --clear-screen=false src/server/index.ts",
1111
"tsc": "tsc",
12+
"tsc:on": "tsc --watch",
1213
"lint": "eslint 'src/**/*.{ts,tsx}'",
1314
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
1415
"format": "prettier --write 'src/**/*.{ts,tsx}'",

src/client/components/Courses/Course/index.tsx

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Edit, OpenInNew } from '@mui/icons-material'
2-
import { Alert, Box, Button, Checkbox, Container, FormControlLabel, Input, Modal, Paper, Skeleton, Tab, Tooltip, Typography } from '@mui/material'
2+
import { Alert, Box, Button, Checkbox, Container, FormControlLabel, Input, Modal, Paper, Skeleton, Stack, Tab, TextField, Tooltip, Typography } from '@mui/material'
33
import { enqueueSnackbar } from 'notistack'
4-
import { useState } from 'react'
4+
import { useEffect, useState } from 'react'
55
import { useTranslation } from 'react-i18next'
6-
import { Link, Route, Routes, useParams } from 'react-router-dom'
6+
import { Form, Link, Route, Routes, useParams } from 'react-router-dom'
77

88
import { PUBLIC_URL } from '../../../../config'
99
import useCourse from '../../../hooks/useCourse'
1010
import useCurrentUser from '../../../hooks/useCurrentUser'
1111
import { useCreatePromptMutation, useDeletePromptMutation } from '../../../hooks/usePromptMutation'
1212
import usePrompts from '../../../hooks/usePrompts'
13-
import type { Message as MessageType } from '../../../types'
13+
import type { Message as MessageType, Responsebility } from '../../../types'
1414
import Conversation from '../../Chat/Conversation'
1515
import SystemMessage from '../../Chat/SystemMessage'
1616
import Rag from '../../Rag/Rag'
@@ -21,23 +21,30 @@ import Stats from './Stats'
2121
import { RouterTabs } from '../../common/RouterTabs'
2222
import Discussion from './Discussions'
2323
import { ApiErrorView } from '../../common/ApiErrorView'
24+
import apiClient from '../../../util/apiClient'
25+
import { t } from 'i18next'
2426

2527
const Course = () => {
2628
const [showTeachers, setShowTeachers] = useState(false)
2729

2830
const [activityPeriodFormOpen, setActivityPeriodFormOpen] = useState(false)
29-
31+
const [responsibilities, setResponsibilities] = useState<Responsebility[]>([])
3032
const { id } = useParams() as { id: string }
3133
const { t, i18n } = useTranslation()
3234

3335
const { language } = i18n
3436

3537
const { user, isLoading: userLoading } = useCurrentUser()
36-
37-
const { data: course, isSuccess: isCourseSuccess, error } = useCourse(id)
38+
const { data: course, isSuccess: isCourseSuccess, error, refetch: refetchCourse } = useCourse(id)
39+
console.log(course)
3840
if (error) {
3941
return <ApiErrorView error={error} />
4042
}
43+
useEffect(() => {
44+
if(isCourseSuccess){
45+
setResponsibilities(course?.responsibilities)
46+
}
47+
}, [isCourseSuccess])
4148

4249
if (userLoading || !user || !isCourseSuccess) return null
4350

@@ -97,7 +104,24 @@ const Course = () => {
97104
boxSizing: 'borderBox',
98105
height: '40px',
99106
}
107+
const handleAddResponsible = async (e) => {
108+
e.preventDefault()
109+
const username = e.target.username.value
110+
const result = await apiClient.post(`/courses/${course.id}/responsibilities/assign`, { username: username })
111+
if(result.status === 200){
112+
const responsibility = result.data
113+
setResponsibilities([...responsibilities, responsibility])
114+
refetchCourse()
115+
}
100116

117+
}
118+
const handleRemoveResponsibility = async (responsibility) => {
119+
const result = await apiClient.post(`/courses/${course.id}/responsibilities/remove`, { username: responsibility.user?.username })
120+
if(result.status === 200){
121+
const filteredResponsibilities = responsibilities.filter((r) => r.id !== responsibility.id)
122+
setResponsibilities(filteredResponsibilities)
123+
}
124+
}
101125
return (
102126
<Container sx={{ mt: '4rem', mb: '10rem' }} maxWidth="xl">
103127
<Alert severity={getInfoSeverity()}>
@@ -182,13 +206,20 @@ const Course = () => {
182206
{showTeachers ? t('admin:hideTeachers') : t('admin:showTeachers')}
183207
</Button>
184208
{showTeachers && (
185-
<ul>
186-
{course.responsibilities.map((responsibility) => (
187-
<li key={responsibility.id}>
188-
{responsibility.user.last_name} {responsibility.user.first_names}
189-
</li>
190-
))}
191-
</ul>
209+
<Box>
210+
<Form onSubmit={handleAddResponsible}>
211+
<TextField name="username" placeholder={'käyttäjänimi: '}></TextField>
212+
<Button type={'submit'}>Lisää</Button>
213+
</Form>
214+
<Stack sx={{margin: 1, padding: 1, borderColor: 'gray', borderWidth: 1, borderStyle: 'solid'}}>
215+
{responsibilities.map((responsibility) => (
216+
<Box key={responsibility.id} sx={{display: 'flex', alignItems: 'center', padding: 1}}>
217+
<Typography>{responsibility.user.last_name} {responsibility.user.first_names}</Typography>
218+
<AssignedResponsibilityManagement handleRemove={() => {handleRemoveResponsibility(responsibility)}} responsibility={responsibility}/>
219+
</Box>
220+
))}
221+
</Stack>
222+
</Box>
192223
)}
193224
</>
194225
)}
@@ -218,6 +249,21 @@ const Course = () => {
218249
)
219250
}
220251

252+
const AssignedResponsibilityManagement = ({responsibility, handleRemove}) => {
253+
const { t, i18n } = useTranslation()
254+
if(!responsibility.createdByUserId){
255+
return (
256+
<Stack direction={'row'} sx={{marginLeft: 'auto', alignItems: 'center', height: '1rem'}} >
257+
</Stack>
258+
)}
259+
return (
260+
<Stack direction={'row'} sx={{marginLeft: 'auto', alignItems: 'center', height: '1rem'}} >
261+
<Typography>{t('course:customResponsibility')}</Typography>
262+
<Button onClick={handleRemove}>{t('course:remove')}</Button>
263+
</Stack>
264+
)
265+
}
266+
221267
const Prompts = ({ courseId }: { courseId: string }) => {
222268
const { t } = useTranslation()
223269
const [name, setName] = useState('')

src/client/hooks/useChatScroll.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ export const useChatScroll = (appContainerRef, endOfConversationRef) => {
8383
}
8484

8585
const smoothScrollTo = (duration: number) => {
86-
if(!shouldScroll.current)
87-
{
86+
if (!shouldScroll.current) {
8887
return
8988
}
9089
//should scroll but there might be another scroll frame going so first it should be cancelled

src/client/hooks/useUpdateUrlLang.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const useUpdateUrlLang = () => {
1010
const { user } = useCurrentUser()
1111
const [params, setParams] = useSearchParams()
1212
const langParam = params.get('lang')
13-
const [lang, setStorageLang] = useLocalStorageState('lang', 'en')
13+
const [lang, setStorageLang] = useLocalStorageState('lang', 'en')
1414
useEffect(() => {
1515
const updatedLangFromLocal = localStorage.getItem('lang')
1616

src/client/locales/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@
282282
"stats": "Statistics",
283283
"discussions": "Discussions",
284284
"prompts": "Prompts",
285-
"rag": "RAG"
285+
"rag": "RAG",
286+
"remove": "remove",
287+
"customResponsibility": "manually added"
286288
},
287289
"tooltip": {
288290
"copied": "Copied!"

src/client/locales/fi.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@
282282
"stats": "Statistiikka",
283283
"discussions": "Keskustelut",
284284
"prompts": "Alustukset",
285-
"rag": "RAG"
285+
"rag": "RAG",
286+
"remove": "poista",
287+
"customResponsibility": "manuaalisesti lisätty"
286288
},
287289
"tooltip": {
288290
"copied": "Kopioitu!"

src/client/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ export interface Enrolment {
107107
}
108108
}
109109

110-
interface Responsebility {
110+
export interface Responsebility {
111111
id: string
112+
createdByUserId: string | null
112113
user: {
113114
id: string
114115
username: string
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { DataTypes } from 'sequelize'
2+
3+
import { Migration } from '../connection'
4+
5+
export const up: Migration = async ({ context: queryInterface }) => {
6+
await queryInterface.addColumn('responsibilities', 'created_by_user_id', {
7+
type: DataTypes.STRING,
8+
allowNull: true,
9+
defaultValue: null,
10+
})
11+
}
12+
13+
export const down: Migration = async ({ context: queryInterface }) => {
14+
await queryInterface.removeColumn('responsibilities', 'created_by_user_id')
15+
}

src/server/db/models/responsibilities.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class Responsibility extends Model<InferAttributes<Responsibility>, InferCreatio
1111
declare user?: NonAttribute<User>
1212

1313
declare chatInstanceId: string
14+
15+
declare createdByUserId: CreationOptional<string>
1416
}
1517

1618
Responsibility.init(
@@ -29,6 +31,11 @@ Responsibility.init(
2931
type: DataTypes.STRING,
3032
allowNull: false,
3133
},
34+
createdByUserId: {
35+
// tells who manually created the responsibility, (null = created by updater)
36+
type: DataTypes.STRING,
37+
allowNull: true,
38+
},
3239
},
3340
{
3441
underscored: true,

0 commit comments

Comments
 (0)