From 0d9370349fc5bb1e16bebfe4f4a3f90473d8a1a6 Mon Sep 17 00:00:00 2001 From: idrissDouelfiqar <idriss.douelfiqar@marketingconfort.com> Date: Thu, 8 Aug 2024 12:21:29 +0100 Subject: [PATCH] job list view job details update display --- src/app/freelancers/job/[id]/page.tsx | 2 +- src/app/freelancers/page.tsx | 2 +- src/shared/_mock/_job.ts | 91 +++----- .../sections/job/job-details-content.tsx | 8 +- src/shared/sections/job/job-item.tsx | 200 +++++++----------- src/shared/sections/job/job-list.tsx | 8 +- .../sections/job/view/job-details-view.tsx | 32 +-- .../sections/job/view/job-list-view.tsx | 8 +- 8 files changed, 146 insertions(+), 205 deletions(-) diff --git a/src/app/freelancers/job/[id]/page.tsx b/src/app/freelancers/job/[id]/page.tsx index 7a4d9d8..f278a6b 100644 --- a/src/app/freelancers/job/[id]/page.tsx +++ b/src/app/freelancers/job/[id]/page.tsx @@ -5,7 +5,7 @@ import { JobDetailsView } from 'src/shared/sections/job/view'; // ---------------------------------------------------------------------- -export const metadata = { title: `Job details | Dashboard - ${CONFIG.site.name}` }; +export const metadata = { title: `Job details | Freelance - ${CONFIG.site.name}` }; type Props = { params: { id: string }; diff --git a/src/app/freelancers/page.tsx b/src/app/freelancers/page.tsx index 7530162..30d8b82 100644 --- a/src/app/freelancers/page.tsx +++ b/src/app/freelancers/page.tsx @@ -4,7 +4,7 @@ import { BlankView } from 'src/shared/sections/blank/view'; // ---------------------------------------------------------------------- -export const metadata = { title: `Dashboard - ${CONFIG.site.name}` }; +export const metadata = { title: `Freelance - ${CONFIG.site.name}` }; export default function Page() { return <BlankView title="Freelancers" />; diff --git a/src/shared/_mock/_job.ts b/src/shared/_mock/_job.ts index 5cf4f54..b39a55f 100644 --- a/src/shared/_mock/_job.ts +++ b/src/shared/_mock/_job.ts @@ -4,7 +4,7 @@ import { _mock } from './_mock'; export const JOB_DETAILS_TABS = [ { label: 'Contenu du poste', value: 'contenu' }, - { label: 'Candidats', value: 'candidats' }, + // { label: 'Candidats', value: 'candidats' }, ]; export const JOB_SKILL_OPTIONS = [ @@ -29,19 +29,6 @@ export const JOB_SKILL_OPTIONS = [ export const JOB_BADGE_OPTIONS = ['Badge A', 'Badge B', 'Badge C']; -export const JOB_WORKING_SCHEDULE_OPTIONS = [ - 'Du lundi au vendredi', - 'Disponibilité le week-end', - 'Horaires de jour', -]; - -export const JOB_EMPLOYMENT_TYPE_OPTIONS = [ - { label: 'Temps plein', value: 'Temps plein' }, - { label: 'Temps partiel', value: 'Temps partiel' }, - { label: 'Sur demande', value: 'Sur demande' }, - { label: 'Négociable', value: 'Négociable' }, -]; - export const JOB_EXPERIENCE_OPTIONS = [ { label: 'Aucune expérience', value: 'Aucune expérience' }, { label: "1 an d'expérience", value: "1 an d'expérience" }, @@ -49,19 +36,6 @@ export const JOB_EXPERIENCE_OPTIONS = [ { label: "> 3 ans d'expérience", value: "> 3 ans d'expérience" }, ]; -export const JOB_BENEFIT_OPTIONS = [ - { label: 'Free parking', value: 'Free parking' }, - { label: 'Bonus commission', value: 'Bonus commission' }, - { label: 'Travel', value: 'Travel' }, - { label: 'Device support', value: 'Device support' }, - { label: 'Health care', value: 'Health care' }, - { label: 'Training', value: 'Training' }, - { label: 'Health insurance', value: 'Health insurance' }, - { label: 'Retirement plans', value: 'Retirement plans' }, - { label: 'Paid time off', value: 'Paid time off' }, - { label: 'Flexible work schedule', value: 'Flexible work schedule' }, -]; - export const JOB_PUBLISH_OPTIONS = [ { label: 'Published', value: 'published' }, { label: 'Draft', value: 'draft' }, @@ -80,42 +54,45 @@ const CANDIDATES = [...Array(12)].map((_, index) => ({ avatarUrl: _mock.image.avatar(index), })); -const CONTENT = ` -<h6>Job description</h6> +const CONTENT = `Nous recherchons un développeur freelance expérimenté pour concevoir et développer un site web e-commerce complet. Le site doit être moderne, intuitif et adapté aux besoins de notre clientèle.`; -<p>Occaecati est et illo quibusdam accusamus qui. Incidunt aut et excepturi harum nihil tenetur facilis. Ut omnis voluptates nihil accusantium doloribus eaque debitis.</p> +const PRESTATIONS = ` +<h6>Design et UX/UI :</h6> -<h6>Key responsibilities</h6> +Création d'une interface utilisateur moderne et responsive<br> +Optimisation de l'expérience utilisateur pour améliorer la conversion<br> +<h6>Fonctionnalités principales :</h6> -<ul> - <li>Working with agency for design drawing detail, quotation and local production.</li> - <li>Produce window displays, signs, interior displays, floor plans and special promotions displays.</li> -</ul> -`; +Système de gestion des produits (ajout, modification, suppression)<br> +Intégration de méthodes de paiement sécurisées<br> +Système de gestion des commandes et des livraisons<br> +Interface d'administration pour gérer les produits, commandes et clients<br> +Fonctionnalités de recherche et de filtrage des produits<br> +<h6>Performances et sécurité :</h6> -const PRESTATIONS = ` -<h6>Avantages de l'entreprise</h6> -<ul> - <li>Mutuelle santé prise en charge à 70%</li> - <li>Prévoyance</li> - <li>Participation aux transports à hauteur de 50%</li> - <li>Tickets restaurant (9€/jour)</li> - <li>RTT (12 jours/an)</li> -</ul> +Optimisation du site pour un chargement rapide<br> +Implémentation de mesures de sécurité pour protéger les données des clients<br> +<h6>Support et maintenance :</h6> + +Support technique pendant les trois premiers mois après le lancement +Documentation détaillée pour la gestion du site `; const EXIGENCES = ` -<h6>Profil recherché</h6> -<ul> - <li>Diplôme Bac+5 en informatique ou équivalent</li> - <li>3 à 5 ans d'expérience en développement web</li> -</ul> - -<h6>Compétences techniques requises</h6> -<ul> - <li>JavaScript / TypeScript</li> - <li>React et React Native</li> -</ul> +<h6>Technologies requises :</h6> + +Langages : HTML5, CSS3, JavaScript<br> +Frameworks : React ou Angular pour le front-end, Spring Boot pour le back-end<br> +Bases de données : MySQL ou PostgreSQL<br> +Autres : API REST, Docker, Git<br> +<h6>Compétences souhaitées :</h6> + +Expérience confirmée en développement web full-stack<br> +Capacité à créer des interfaces utilisateur réactives et intuitives<br> +Connaissance des pratiques de SEO<br> +Expérience avec les méthodes de paiement en ligne (ex : Stripe, PayPal)<br> +Connaissance des bonnes pratiques de sécurité web<br> + `; export const _jobs = [...Array(12)].map((_, index) => { @@ -143,7 +120,7 @@ export const _jobs = [...Array(12)].map((_, index) => { totalViews: _mock.number.nativeL(index), prestations: PRESTATIONS, // Add a sample value exigences: EXIGENCES, // Add a sample value - certifs: ['Sample certification'], // Add a sample array with one certification + certifs: ['certification A, Certification B'], // Add a sample array with one certification createdAt: _mock.time(index), candidates: CANDIDATES, }; diff --git a/src/shared/sections/job/job-details-content.tsx b/src/shared/sections/job/job-details-content.tsx index b44e4e1..3f5cb09 100644 --- a/src/shared/sections/job/job-details-content.tsx +++ b/src/shared/sections/job/job-details-content.tsx @@ -25,9 +25,14 @@ export function JobDetailsContent({ job }: Props) { const renderContent = ( <Card sx={{ p: 3, gap: 3, display: 'flex', flexDirection: 'column' }}> <Typography variant="h4">{job?.title}</Typography> - <Markdown children={job?.content} /> <Markdown children={job?.prestations} /> + <Stack spacing={2}> + <Typography variant="h6">Certifications</Typography> + <Stack direction="row" alignItems="center" spacing={1}> + {job?.certifs.map((certif, index) => <Chip key={index} label={certif} variant="soft" />)} + </Stack> + </Stack> <Markdown children={job?.exigences} /> {/* <Stack spacing={2}> <Typography variant="h6">Skills</Typography> @@ -35,7 +40,6 @@ export function JobDetailsContent({ job }: Props) { {job?.skills.map((skill) => <Chip key={skill} label={skill} variant="soft" />)} </Stack> </Stack> */} - {/* <Stack spacing={2}> <Typography variant="h6">Benefits</Typography> <Stack direction="row" alignItems="center" spacing={1}> diff --git a/src/shared/sections/job/job-item.tsx b/src/shared/sections/job/job-item.tsx index 500604a..1dafabe 100644 --- a/src/shared/sections/job/job-item.tsx +++ b/src/shared/sections/job/job-item.tsx @@ -1,5 +1,6 @@ import type { IJobItem } from 'src/shared/types/job'; +import React from 'react'; import Box from '@mui/material/Box'; import Link from '@mui/material/Link'; import Card from '@mui/material/Card'; @@ -20,8 +21,23 @@ import { fCurrency } from 'src/utils/format-number'; import { Iconify } from 'src/shared/components/iconify'; import { usePopover, CustomPopover } from 'src/shared/components/custom-popover'; +import { Button, Chip } from '@mui/material'; // ---------------------------------------------------------------------- +function getTimeAgo(date: string | number | Date | null) { + if (date == null) return null; + + const now = new Date(); + const past = new Date(date); + const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000); + + if (diffInSeconds < 60) return "à l'instant"; + if (diffInSeconds < 3600) return `il y a ${Math.floor(diffInSeconds / 60)} minute(s)`; + if (diffInSeconds < 86400) return `il y a ${Math.floor(diffInSeconds / 3600)} heure(s)`; + if (diffInSeconds < 2592000) return `il y a ${Math.floor(diffInSeconds / 86400)} jour(s)`; + if (diffInSeconds < 31536000) return `il y a ${Math.floor(diffInSeconds / 2592000)} mois`; + return `il y a ${Math.floor(diffInSeconds / 31536000)} an(s)`; +} type Props = { job: IJobItem; @@ -29,130 +45,72 @@ type Props = { onEdit: () => void; onDelete: () => void; }; - export function JobItem({ job, onView, onEdit, onDelete }: Props) { - const popover = usePopover(); + // const timeAgo = getTimeAgo(job.createdAt); return ( - <> - <Card> - <IconButton onClick={popover.onOpen} sx={{ position: 'absolute', top: 8, right: 8 }}> - <Iconify icon="eva:more-vertical-fill" /> - </IconButton> - - <Stack sx={{ p: 3, pb: 2 }}> - <Avatar - alt={job.company.name} - src={job.company.logo} - variant="rounded" - sx={{ width: 48, height: 48, mb: 2 }} - /> - - <ListItemText - sx={{ mb: 1 }} - primary={ - <Link component={RouterLink} href={paths.freelancers.details(job.id)} color="inherit"> - {job.title} - </Link> - } - secondary={`Posted date: ${fDate(job.createdAt)}`} - primaryTypographyProps={{ typography: 'subtitle1' }} - secondaryTypographyProps={{ - mt: 1, - component: 'span', - typography: 'caption', - color: 'text.disabled', - }} - /> - - <Stack - spacing={0.5} - direction="row" - alignItems="center" - sx={{ color: 'primary.main', typography: 'caption' }} - > - <Iconify width={16} icon="solar:users-group-rounded-bold" /> - {job.candidates.length} candidates - </Stack> - </Stack> - - <Divider sx={{ borderStyle: 'dashed' }} /> - - {/* <Box rowGap={1.5} display="grid" gridTemplateColumns="repeat(2, 1fr)" sx={{ p: 3 }}> - {[ - { - label: job.experience, - icon: <Iconify width={16} icon="carbon:skill-level-basic" sx={{ flexShrink: 0 }} />, - }, - { - label: job.employmentTypes.join(', '), - icon: <Iconify width={16} icon="solar:clock-circle-bold" sx={{ flexShrink: 0 }} />, - }, - { - label: job.salary.negotiable ? 'Negotiable' : fCurrency(job.salary.price), - icon: <Iconify width={16} icon="solar:wad-of-money-bold" sx={{ flexShrink: 0 }} />, - }, - { - label: job.role, - icon: <Iconify width={16} icon="solar:user-rounded-bold" sx={{ flexShrink: 0 }} />, - }, - ].map((item) => ( - <Stack - key={item.label} - spacing={0.5} - flexShrink={0} - direction="row" - alignItems="center" - sx={{ color: 'text.disabled', minWidth: 0 }} + <Card + onClick={onView} + sx={{ + cursor: 'pointer', + '&:hover': { bgcolor: 'action.hover' }, + position: 'relative', + pt: 5, + }} + > + <Box sx={{ position: 'absolute', left: 16 }}> + <Avatar + src={job.company?.logo || '/path/to/default-logo.png'} + alt={job.company?.name || 'Company Logo'} + variant="rounded" + sx={{ width: 40, height: 40 }} + /> + </Box> + + <Box sx={{ px: 10, pb: 3 }}> + <ListItemText + sx={{ mb: 1 }} + primary={ + <Link + component={RouterLink} + sx={{ fontSize: '18px' }} + href={paths.freelancers.details(job.id)} + color="inherit" > - {item.icon} - <Typography variant="caption" noWrap> - {item.label} - </Typography> - </Stack> - ))} - </Box> */} - </Card> - - <CustomPopover - open={popover.open} - anchorEl={popover.anchorEl} - onClose={popover.onClose} - slotProps={{ arrow: { placement: 'right-top' } }} - > - <MenuList> - <MenuItem - onClick={() => { - popover.onClose(); - onView(); - }} - > - <Iconify icon="solar:eye-bold" /> - View - </MenuItem> - - <MenuItem - onClick={() => { - popover.onClose(); - onEdit(); - }} - > - <Iconify icon="solar:pen-bold" /> - Edit - </MenuItem> - - <MenuItem - onClick={() => { - popover.onClose(); - onDelete(); - }} - sx={{ color: 'error.main' }} - > - <Iconify icon="solar:trash-bin-trash-bold" /> - Delete - </MenuItem> - </MenuList> - </CustomPopover> - </> + {job.title} + </Link> + } + /> + + <Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}> + {`${job.salary.price ? `${job.salary.price}€` : 'Salaire non spécifié'} • ${job.totalViews} vues`} + </Typography> + + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + overflow: 'hidden', + textOverflow: 'ellipsis', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + lineHeight: '1.5em', + height: '3em', + mb: 1, + }} + > + {job.content.slice(0, 200)} + </Typography> + + <Typography variant="body2" sx={{ fontWeight: 'bold', mb: 1 }}> + {job.certifs.join(', ')} + </Typography> + + <Typography variant="caption" sx={{ color: 'text.secondary', display: 'block' }}> + {`Il y a ${getTimeAgo(job.createdAt)} • Client #${job.id.slice(0, 8)}`} + </Typography> + </Box> + </Card> ); } diff --git a/src/shared/sections/job/job-list.tsx b/src/shared/sections/job/job-list.tsx index 7cafc43..b6cc05a 100644 --- a/src/shared/sections/job/job-list.tsx +++ b/src/shared/sections/job/job-list.tsx @@ -40,9 +40,11 @@ export function JobList({ jobs }: Props) { return ( <> <Box - gap={3} - display="grid" - gridTemplateColumns={{ xs: 'repeat(1, 1fr)', sm: 'repeat(2, 1fr)', md: 'repeat(3, 1fr)' }} + sx={{ + display: 'flex', + flexDirection: 'column', + gap: 2, // Espacement vertical entre les éléments + }} > {jobs.map((job) => ( <JobItem diff --git a/src/shared/sections/job/view/job-details-view.tsx b/src/shared/sections/job/view/job-details-view.tsx index 93c6a30..a14cf69 100644 --- a/src/shared/sections/job/view/job-details-view.tsx +++ b/src/shared/sections/job/view/job-details-view.tsx @@ -29,11 +29,11 @@ type Props = { export function JobDetailsView({ job }: Props) { const tabs = useTabs('content'); - const [publish, setPublish] = useState(job?.publish); + // const [publish, setPublish] = useState(job?.publish); - const handleChangePublish = useCallback((newValue: string) => { - setPublish(newValue); - }, []); + // const handleChangePublish = useCallback((newValue: string) => { + // setPublish(newValue); + // }, []); const renderTabs = ( <Tabs value={tabs.value} onChange={tabs.onChange} sx={{ mb: { xs: 3, md: 5 } }}> @@ -57,19 +57,19 @@ export function JobDetailsView({ job }: Props) { return ( <DashboardContent> - <JobDetailsToolbar + {/* <JobDetailsToolbar backLink={paths.freelancers.jobs} - editLink={paths.freelancers.edit(`${job?.id}`)} - liveLink="#" - publish={publish || ''} - onChangePublish={handleChangePublish} - publishOptions={JOB_PUBLISH_OPTIONS} - /> - {renderTabs} - - {tabs.value === 'content' && <JobDetailsContent job={job} />} - - {tabs.value === 'candidates' && <JobDetailsCandidates candidates={job?.candidates ?? []} />} + // editLink={paths.freelancers.edit(`${job?.id}`)} + // liveLink="#" + // publish={publish || ''} + // onChangePublish={handleChangePublish} + // publishOptions={JOB_PUBLISH_OPTIONS} + /> */} + {/* {renderTabs} */} + + {/* {tabs.value === 'content' && <JobDetailsContent job={job} />} */} + <JobDetailsContent job={job} /> + {/* {tabs.value === 'candidates' && <JobDetailsCandidates candidates={job?.candidates ?? []} />} */} </DashboardContent> ); } diff --git a/src/shared/sections/job/view/job-list-view.tsx b/src/shared/sections/job/view/job-list-view.tsx index 416f48c..17ba883 100644 --- a/src/shared/sections/job/view/job-list-view.tsx +++ b/src/shared/sections/job/view/job-list-view.tsx @@ -20,9 +20,9 @@ import { _jobs, _roles, JOB_SORT_OPTIONS, - JOB_BENEFIT_OPTIONS, - JOB_EXPERIENCE_OPTIONS, - JOB_EMPLOYMENT_TYPE_OPTIONS, + // JOB_BENEFIT_OPTIONS, + // JOB_EXPERIENCE_OPTIONS, + // JOB_EMPLOYMENT_TYPE_OPTIONS, } from 'src/shared/_mock'; import { Iconify } from 'src/shared/components/iconify'; @@ -123,7 +123,7 @@ export function JobListView() { <CustomBreadcrumbs heading="List" links={[ - { name: 'Dashboard', href: paths.dashboard.root }, + { name: 'Freelance', href: paths.freelancers.root }, { name: 'Job', href: paths.freelancers.jobs }, { name: 'List' }, ]} -- GitLab