Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Malla favorita #428

Merged
merged 14 commits into from
Jul 13, 2024
52 changes: 26 additions & 26 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frontend/src/assets/editBlue.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/assets/starFull.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/assets/starOutline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion frontend/src/pages/mod/userViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const UserViewer = (): JSX.Element => {
const { status: studentDataStatus, error: studentDataError, data: studentInfo } = useQuery({
queryKey: ['studentInfo', userRut],
retry: false,
onSuccess: (data) => {
onSuccess: (data: Student | null) => {
if (data !== undefined && data !== null) {
setSearchingPlanModalIsOpen(false)
if (authState?.setStudent !== null) {
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/pages/planner/dialogs/SavePlanModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { memo, useState } from 'react'
import { memo, useEffect, useState } from 'react'
import TextInputModal from '../../../components/TextInputModal'

const SavePlanModal = ({ isOpen, onClose, savePlan }: { isOpen: boolean, onClose: Function, savePlan: Function }): JSX.Element => {
const SavePlanModal = ({ isOpen, onClose, savePlan, defaultValue = '' }: { isOpen: boolean, onClose: Function, savePlan: Function, defaultValue?: string }): JSX.Element => {
const [planName, setPlanName] = useState<string>('')

const isSaveButtonDisabled: boolean = planName === ''

useEffect(() => {
setPlanName(defaultValue)
}, [defaultValue])

return (
<TextInputModal
title="Nombre de la planificación"
Expand Down
89 changes: 85 additions & 4 deletions frontend/src/pages/user/CurriculumList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { Spinner } from '../../components/Spinner'
import { toast } from 'react-toastify'
import AlertModal from '../../components/AlertModal'

import SavePlanModal from '../planner/dialogs/SavePlanModal'
import useDummyModal from '../../utils/useDummyModal'

const isApiError = (err: any): err is ApiError => {
return err.status !== undefined
}
Expand All @@ -16,8 +19,40 @@ const CurriculumList = (): JSX.Element => {
const [loading, setLoading] = useState <boolean>(true)
const [popUpAlert, setPopUpAlert] = useState({ isOpen: false, id: '' })

const [currentEditedId, setCurrentEditedId] = useState <string>('')
const [currentEditedName, setCurrentEditedName] = useState <string>('')

const { isModalOpen: isSavePlanModalOpen, openModal: openSavePlanModal, closeModal: closeSavePlanModal } = useDummyModal()

function compare (a: LowDetailPlanView, b: LowDetailPlanView): number {
const timestampA = Date.parse(a.updated_at)
const timestampB = Date.parse(b.updated_at)
if (timestampA < timestampB) {
return -1
}
if (timestampA > timestampB) {
return 1
}
return 0
}

function arrayMove (arr: any[], oldIndex: number, newIndex: number): void {
if (newIndex >= arr.length) {
let k = newIndex - arr.length + 1
while ((k--) !== 0) {
arr.push(undefined)
}
}
arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0])
};

const readPlans = async (): Promise<void> => {
const response = await DefaultService.readPlans()
response.sort(compare).reverse()
const index = response.findIndex((plan) => plan.is_favorite)
if (index !== -1) {
arrayMove(response, index, 0)
}
setPlans(response)
setLoading(false)
}
Expand All @@ -32,11 +67,49 @@ const CurriculumList = (): JSX.Element => {
})
}
})
}, [])
})

async function handleFavourite (id: string, planName: string, fav: boolean): Promise<void> {
try {
await DefaultService.updatePlanMetadata(id, undefined, !fav)
await readPlans()
console.log('plan updated')
} catch (err) {
console.log(err)
if (isApiError(err) && err.status === 401) {
console.log('token invalid or expired, loading re-login page')
toast.error('Token invalido. Redireccionando a pagina de inicio...')
}
}
}

function openEditModal (id: string, name: string): void {
openSavePlanModal()
setCurrentEditedId(id)
setCurrentEditedName(name)
}

async function editPlanName (planName: string): Promise<void> {
if (planName === null || planName === '') return
try {
await DefaultService.updatePlanMetadata(currentEditedId, planName, undefined)
await readPlans()
console.log('plan updated')
toast.success('Malla actualizada exitosamente')
} catch (err) {
console.log(err)
if (isApiError(err) && err.status === 401) {
console.log('token invalid or expired, loading re-login page')
toast.error('Token invalido. Redireccionando a pagina de inicio...')
}
}
closeSavePlanModal()
setCurrentEditedId('')
setCurrentEditedName('')
}

async function handleDelete (id: string): Promise<void> {
try {
console.log('click', id)
await DefaultService.deletePlan(id)
await readPlans()
console.log('plan deleted')
Expand All @@ -49,6 +122,7 @@ const CurriculumList = (): JSX.Element => {
}
}
}

function handlePopUpAlert (isCanceled: boolean): void {
if (!isCanceled) {
void handleDelete(popUpAlert.id)
Expand All @@ -58,6 +132,7 @@ const CurriculumList = (): JSX.Element => {

return (
<div className="flex mb-4 h-full w-full">
<SavePlanModal isOpen={isSavePlanModalOpen} onClose={closeSavePlanModal} savePlan={editPlanName} defaultValue={currentEditedName}/>
<AlertModal title={'Eliminar malla'} isOpen={popUpAlert.isOpen} close={handlePopUpAlert}>{'¿Estás seguro/a de que deseas eliminar esta malla? Esta accion es irreversible'}</AlertModal>
<div className="m-3 w-full">
<div className="flex gap-4 items-center">
Expand All @@ -78,7 +153,7 @@ const CurriculumList = (): JSX.Element => {
<table className="w-full text-sm text-left text-gray-500">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 ">
<tr className="border-b-4 border-gray-600">
{/* <th></th> para favourite */}
<th scope="col" className="px-6 py-3"><span className="sr-only">Fav</span></th>
<th scope="col" className="px-6 py-3">Nombre</th>
<th scope="col" className="px-6 py-3">Fecha Creación</th>
<th scope="col" className="px-6 py-3">Fecha Modificación</th>
Expand All @@ -89,7 +164,13 @@ const CurriculumList = (): JSX.Element => {
<tbody className='bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600'>
{plans?.map((plan: LowDetailPlanView) => {
return (
<CurriculumListRow key={plan.id} handleDelete={(id: string) => { setPopUpAlert({ isOpen: true, id }) }} curriculum={plan}/>
<CurriculumListRow
key={plan.id}
handleDelete={(id: string) => { setPopUpAlert({ isOpen: true, id }) }}
curriculum={plan}
handleFavourite ={handleFavourite}
openPlanNameModal={openEditModal}
/>
)
})}
</tbody>
Expand Down
77 changes: 51 additions & 26 deletions frontend/src/pages/user/CurriculumListRow.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import { type LowDetailPlanView } from '../../client'
import { Link } from '@tanstack/react-router'
import { ReactComponent as StarFull } from '../../assets/starFull.svg'
import { ReactComponent as StarOutline } from '../../assets/starOutline.svg'
import { ReactComponent as EditIcon } from '../../assets/editBlue.svg'

const CurriculumListRow = ({ curriculum, handleDelete, impersonateRut }: { curriculum: LowDetailPlanView, handleDelete?: Function, impersonateRut?: string }): JSX.Element => {
interface RowProps {
curriculum: LowDetailPlanView
handleDelete?: Function
handleFavourite?: Function
openPlanNameModal?: Function
impersonateRut?: string
}

const CurriculumListRow = ({ curriculum, handleDelete, handleFavourite, openPlanNameModal, impersonateRut }: RowProps): JSX.Element => {
function getDateString (date: string): string {
const mydate = date.split('T')[0].split('-').reverse().join('-')
return mydate
}

return (
<tr className='bg-white border-b hover:bg-gray-50 '>
<td className='px-6 py-4'>
{handleFavourite !== undefined &&
<button onClick={() => handleFavourite(curriculum.id, curriculum.name, curriculum.is_favorite)}>{curriculum.is_favorite ? <StarFull /> : <StarOutline />}</button>
}
</td>
<th scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
{impersonateRut !== undefined
? <Link
Expand All @@ -20,42 +36,51 @@ const CurriculumListRow = ({ curriculum, handleDelete, impersonateRut }: { curri
}}
>{curriculum.name}
</Link>
: <Link
: <div className='flex flex-row'><Link
className='text-blue-600'
to="/planner/$plannerId"
params={{
plannerId: curriculum.id
}}
>{curriculum.name}
</Link>
{openPlanNameModal !== undefined &&
<div className="hover-text">
<button className='pl-3' onClick={() => openPlanNameModal(curriculum.id, curriculum.name) }><EditIcon/></button>
<span className="tooltip-text font-thin">Editar nombre</span>
</div>
}
</div>
}
</th>
<td className='px-6 py-4'>{getDateString(curriculum.created_at)}</td>
<td className='px-6 py-4'>{getDateString(curriculum.updated_at)}</td>
<td className='px-6 py-4 text-right'><div className='space-x-4 items-center'>
{impersonateRut !== undefined
? <Link
className='text-blue-600'
to="/mod/planner/$userRut/$plannerId"
params={{
userRut: impersonateRut,
plannerId: curriculum.id
}}
>Editar
</Link>
: <Link
className='text-blue-600'
to="/planner/$plannerId"
params={{
plannerId: curriculum.id
}}
>Editar
</Link>
}
{handleDelete !== undefined &&
<button className='text-red-600' onClick={() => handleDelete(curriculum.id)}>Eliminar</button>
}
</div></td>
<td className='px-6 py-4 text-right'>
<div className='flex space-x-4 items-center'>
{impersonateRut !== undefined
? <Link
className='text-blue-600'
to="/mod/planner/$userRut/$plannerId"
params={{
userRut: impersonateRut,
plannerId: curriculum.id
}}
>Editar
</Link>
: <Link
className='text-blue-600'
to="/planner/$plannerId"
params={{
plannerId: curriculum.id
}}
>Editar
</Link>
}
{handleDelete !== undefined &&
<button className='text-red-600' onClick={() => handleDelete(curriculum.id)}>Eliminar</button>
}
</div>
</td>
</tr>
)
}
Expand Down
Loading