diff --git a/backend/src/controllers/job.ts b/backend/src/controllers/job.ts index 384e0b5..4925378 100644 --- a/backend/src/controllers/job.ts +++ b/backend/src/controllers/job.ts @@ -19,7 +19,7 @@ class Job { async getJobsByCustomerId(req: Request, res: Response, next: NextFunction) { try { - const jobs = await this.userService.getJobsByCustomerId(+req.params.id); + const jobs = await this.userService.getJobsByCustomerId(+req.params.id,+req.query.skip,+req.query.limit); res.json(jobs); } catch (e) { return next(e); diff --git a/backend/src/controllers/user.ts b/backend/src/controllers/user.ts index eb7cd1b..30ce1fc 100644 --- a/backend/src/controllers/user.ts +++ b/backend/src/controllers/user.ts @@ -58,14 +58,48 @@ class User { } } - async getCustomer(req: Request, res: Response, next: NextFunction) { + async createCustomer(req: Request, res: Response, next: NextFunction) { try { - const users = await this.userService.getCustomer(+req.query.id); + const response = await this.userService.createCustomer(req.body); + res.json(response); + } catch (e) { + return next(e); + } + } + async updateCustomerById(req: Request, res: Response, next: NextFunction) { + try { + const response = await this.userService.updateCustomerById(+req.params.id,req.body); + res.json(response); + } catch (e) { + return next(e); + } + } + async getCustomerById(req: Request, res: Response, next: NextFunction) { + try { + const users = await this.userService.getCustomerById(+req.params.id); res.json(users); } catch (e) { return next(e); } - } + } + + async getCustomerComments(req: Request, res: Response, next: NextFunction) { + try { + const customerComments = await this.userService.getCustomerComments(+req.params.id,+req.query.skip,+req.query.limit); + res.json(customerComments); + } catch (e) { + return next(e); + } + } + + async createCommentForCustomer(req: Request, res: Response, next: NextFunction) { + try { + const response = await this.userService.createCommentForCustomer(+req.params.id,req.body); + res.json(response); + } catch (e) { + return next(e); + } + } } export default User; \ No newline at end of file diff --git a/backend/src/models/Comments.ts b/backend/src/models/BloggerComments.ts similarity index 92% rename from backend/src/models/Comments.ts rename to backend/src/models/BloggerComments.ts index 0eab5af..6959005 100644 --- a/backend/src/models/Comments.ts +++ b/backend/src/models/BloggerComments.ts @@ -15,6 +15,6 @@ const commentsSchema = new mongoose.Schema({ comment:mongoose.Schema.Types.String, score: mongoose.Schema.Types.Number, subs_came: mongoose.Schema.Types.Number, -}); +},{timestamps: true}); export const Comments = mongoose.model('Comments', commentsSchema); diff --git a/backend/src/models/Customer.ts b/backend/src/models/Customer.ts index 2cdc310..77ddc92 100644 --- a/backend/src/models/Customer.ts +++ b/backend/src/models/Customer.ts @@ -1,25 +1,42 @@ import mongoose, { Schema } from 'mongoose'; +import { IOption } from './Blogger'; export type CustomerDocument = mongoose.Document & { _id: number; - ig_id: number; - username: string; - full_name: string; - profile_picture: string; - biography: string; - website: string; - commentId: Number; + name: String; + surname: String; + profile_picture: String; + location: { + country: IOption; + city: IOption; + }; + contact: { + mail: String; + phone: String; + link: String; + } }; const customerSchema = new mongoose.Schema({ - _id:mongoose.Schema.Types.Number, - ig_id:mongoose.Schema.Types.Number, - username:Schema.Types.String, - full_name:Schema.Types.String, - profile_picture:Schema.Types.String, - biography:Schema.Types.String, - website:Schema.Types.String, - commentId: {type:Number, ref: 'Comments'}, -}, { timestamps: true, _id:false }); + _id: mongoose.Schema.Types.Number, + name: Schema.Types.String, + surname: Schema.Types.String, + profile_picture: Schema.Types.String, + location:{ + country:{ + label:Schema.Types.String, + value:Schema.Types.String + }, + city:{ + label:Schema.Types.String, + value:Schema.Types.String + } + }, + contact:{ + mail: Schema.Types.String, + phone: Schema.Types.String, + link: Schema.Types.String, + } +}, { timestamps: true, _id: false }); export const Customer = mongoose.model('Customers', customerSchema); diff --git a/backend/src/models/CustomerComments.ts b/backend/src/models/CustomerComments.ts new file mode 100644 index 0000000..4fdc8e2 --- /dev/null +++ b/backend/src/models/CustomerComments.ts @@ -0,0 +1,18 @@ +import mongoose from 'mongoose'; + + +export type CommentsDocumnet = mongoose.Document & { + customerId:Number; + bloggerId:Number; + comment:String; + score:Number; +}; + +const commentsSchema = new mongoose.Schema({ + customerId:{type: Number, ref: 'Customers'}, + bloggerId:{type: Number, ref: 'Bloggers'}, + comment:mongoose.Schema.Types.String, + score:mongoose.Schema.Types.Number, +},{timestamps: true}); + +export const Comments = mongoose.model('CustomerComments', commentsSchema); diff --git a/backend/src/routes/user.route.ts b/backend/src/routes/user.route.ts index 4148988..b63727d 100644 --- a/backend/src/routes/user.route.ts +++ b/backend/src/routes/user.route.ts @@ -18,8 +18,25 @@ user.post('/blogger/:id/comment', (req, res, next) => { userControllerInstance.createCommentForBlogger(req, res, next); }); -user.get('/customer', (req, res, next) => { - userControllerInstance.getCustomer(req, res, next); +user.post('/customer', (req, res, next) => { + userControllerInstance.createCustomer(req, res, next); }); +user.get('/customer/:id', (req, res, next) => { + userControllerInstance.getCustomerById(req, res, next); +}); + +user.put('/customer/:id', (req, res, next) => { + userControllerInstance.updateCustomerById(req, res, next); +}); + +user.get('/customer/:id/comments', (req, res, next) => { + userControllerInstance.getCustomerComments(req, res, next); +}); + +user.post('/customer/:id/comment', (req, res, next) => { + userControllerInstance.createCommentForCustomer(req, res, next); +}); + + export default user; \ No newline at end of file diff --git a/backend/src/services/job.ts b/backend/src/services/job.ts index 4730d4e..401e70d 100644 --- a/backend/src/services/job.ts +++ b/backend/src/services/job.ts @@ -16,7 +16,7 @@ export interface ICreateJobBody{ export interface IJobService{ createJob: (id: number, body: ICreateJobBody) => void; - getJobsByCustomerId: (id: number) => Promise; + getJobsByCustomerId: (id: number, skip: number, limit: number) => Promise; getAllJobs: () => Promise; getJobById: (id:string) => Promise; updateStatusJobById: (id:string,status:boolean) => Promise; @@ -47,8 +47,8 @@ class JobService implements IJobService { await job.save(); } - async getJobsByCustomerId(id: number) { - return Jobs.find({customerId:id}); + async getJobsByCustomerId(id: number, skip: number, limit: number) { + return Jobs.find({customerId:id}).skip(+skip).limit(+limit); } async getAllJobs() { diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index e4ba8b9..4538571 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -1,50 +1,63 @@ import {Customer,CustomerDocument} from '../models/Customer'; import {Blogger,BloggerDocument} from '../models/Blogger'; -import {Comments,CommentsDocumnet} from '../models/Comments'; +import {Comments as BlogerComments, CommentsDocumnet as BloggerCD} from '../models/BloggerComments'; +import {Comments as CustomerComments, CommentsDocumnet as CustomerCD} from '../models/CustomerComments'; -export interface ICreateCommentBody{ +export interface ICreateCommentBlogger{ senderId: Number; comment: String; score: Number; subs_came: Number; } -export interface IAverageData { +export interface ICreateCommentCustomer{ + senderId: Number; + comment: String; + score: Number; +} + +export interface IAverageDataBlogger { averageComing: number; averageScore: number; } export interface IUserService{ getBlogger: (id: number) => Promise; - createCommentForBlogger: (id: number, body: ICreateCommentBody) => object; - getBloggerComments: (id:number,skip: number,limit: number) => Promise<{ comments: CommentsDocumnet[]; averageData: IAverageData; }>; - getCustomer: (id: number) => Promise; + createCommentForBlogger: (id: number, body: ICreateCommentBlogger) => object; + getBloggerComments: (id: number,skip: number,limit: number) => Promise<{ comments: BloggerCD[]; averageData: IAverageDataBlogger; }>; + createCustomer: (data: CustomerDocument) => Promise; + updateCustomerById: (_id: number, data: CustomerDocument) => Promise; + getCustomerById: (id: number) => Promise; + createCommentForCustomer: (id: number, body: ICreateCommentCustomer) => Promise + getCustomerComments: (id: number, skip: number,limit: number) => Promise <{comments: CustomerCD[],averageScore: Number}>; } -async function calculateAverageData (id:number,skip:number): Promise { - if(!skip){ - let averageComing: number; - let averageScore: number; - const allCommnets = await Comments.find({bloggerId:id}); - const subsCame = allCommnets.map(e => e.subs_came); - const countSubs:any = subsCame.reduce((a:number, b:number) => a + b, 0); - averageComing = Math.round(countSubs / subsCame.length); - - const score = allCommnets.map(e => e.score); - const countScore:any = score.reduce((a:number, b:number) => a + b, 0); - averageScore = Math.round(countScore / score.length); - return {averageComing,averageScore} - } - return null; +function callculateAvg (data:Number[]) { + const countData:any = data.reduce((a:number, b:number) => a + b, 0); + return Math.round(countData / data.length); +} + +async function calculateAverageDataBlogger (id:number): Promise { + const allComments = await BlogerComments.find({bloggerId:id}); + const subsCame = allComments.map(e => e.subs_came); + const score = allComments.map(e => e.score); + return {averageComing:callculateAvg(subsCame),averageScore:callculateAvg(score)}; +} + +async function calculateAverageScoreCustomer (id:number) { + const comments = await CustomerComments.find({customerId:id}); + const score = comments.map(e => e.score); + return callculateAvg(score); } class UserService implements IUserService { + //Blogger Services async getBlogger(id: number) { return Blogger.findOne({ig_id:id}); } - async createCommentForBlogger(id:number,body:ICreateCommentBody){ - const comment = new Comments({ + async createCommentForBlogger(id:number,body:ICreateCommentBlogger){ + const comment = new BlogerComments({ customerId:body.senderId, bloggerId:id, comment:body.comment, @@ -56,15 +69,50 @@ class UserService implements IUserService { } async getBloggerComments(id:number,skip: number,limit: number){ - const averageData = await calculateAverageData(id,skip); - const comments = await Comments.find({bloggerId:id}).populate('customerId').skip(+skip).limit(+limit); + const averageData = !skip ? await calculateAverageDataBlogger(id) : null; + const comments = await BlogerComments.find({bloggerId:id}).populate('customerId').skip(+skip).limit(+limit); return {comments, averageData}; } - async getCustomer(id: number) { + //Customer Services + async createCustomer(data: CustomerDocument){ + const lastCustomer = await Customer.findOne({}).sort({createdAt:-1}); + const _id = lastCustomer ? lastCustomer._id + 1 : 1; + const newCustomer = new Customer({ + _id , + name: data.name, + surname: data.surname, + profile_picture: data.profile_picture, + location: data.location, + contact: data.contact + }) + return await newCustomer.save(); + } + + async updateCustomerById(_id:number,data: CustomerDocument){ + return await Customer.findOneAndUpdate({_id}, data, {new:true}); + } + + async getCustomerById(id: number) { return Customer.findById(id); } + async createCommentForCustomer(id:number,body:ICreateCommentBlogger){ + const comment = new CustomerComments({ + customerId: id, + bloggerId: body.senderId, + comment: body.comment, + score: body.score, + }); + const newComment = await comment.save(); + return await CustomerComments.findById(newComment._id).populate('bloggerId'); + } + + async getCustomerComments(id:number,skip: number,limit: number){ + const averageScore = !skip ? await calculateAverageScoreCustomer(id) : null; + const comments = await CustomerComments.find({customerId:id}).sort({createdAt:-1}).populate('bloggerId').skip(+skip).limit(+limit); + return {comments, averageScore}; + } } export default UserService; diff --git a/ui/src/app/components/Job/EditJob/attachments/index.tsx b/ui/src/app/components/Job/EditJob/attachments/index.tsx new file mode 100644 index 0000000..fc35338 --- /dev/null +++ b/ui/src/app/components/Job/EditJob/attachments/index.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { attachmentsIconsCustomer } from '../../../../../img'; +import { AttachmentsContainer } from './styles'; +import { v4 as uuidv4 } from 'uuid'; +import FileLoader from '../../../shared/file-loader'; +import withTheme from '../../../../../HOC/withTheme'; +import { defineType } from '../../../../helpers/define'; +import { FILETYPES } from '../../../../../consts/lists'; + +const Attachments = props => { + const [attachments, setAttachments] = React.useState< + { icon: string; file }[] + >([]); + const prevSaveFile = async file => { + const type = defineType(file.type); + const fileCopy = attachments.some(f => f.file.name === file.name); + if (type && !fileCopy) { + setAttachments([ + ...attachments, + { icon: attachmentsIconsCustomer[type], file }, + ]); + } + }; + return ( + + Attached media +
+
+ {attachments.length > 0 && + attachments.map(item => { + return ( + { + setAttachments( + attachments.filter(f => f.file.name !== item.file.name), + ); + }} + className="item" + src={item.icon} + alt="iconAtched" + key={uuidv4()} + /> + ); + })} +
+
+ prevSaveFile(file)} + allowedFormats={Object.values(FILETYPES)} + /> + +
+
+
+ ); +}; + +export default withTheme(Attachments); diff --git a/ui/src/app/components/Job/EditJob/attachments/styles.ts b/ui/src/app/components/Job/EditJob/attachments/styles.ts new file mode 100644 index 0000000..f32e106 --- /dev/null +++ b/ui/src/app/components/Job/EditJob/attachments/styles.ts @@ -0,0 +1,70 @@ +import { IPropsTheme } from './../../../../../types/index'; +import styled from 'styled-components'; + +export const AttachmentsContainer = styled.div` + width: 320px; + margin: 32px auto 15px; + display: flex; + flex-direction: column; + & > .title { + font-family: 'Roboto', sans-serif; + font-style: normal; + font-weight: bold; + font-size: 20px; + line-height: 33px; + text-align: center; + color: #ffffff; + margin-bottom: 25px; + } + & > .attached { + display: flex; + justify-content: space-around; + width: 100%; + height: 55px; + & > .list { + width: 263px; + overflow-x: auto; + display: flex; + padding-bottom: 10px; + ::-webkit-scrollbar { + width: 200px; + height: 8px; + mix-blend-mode: normal; + opacity: 0.5; + border: 1px solid ${({ theme }) => theme && theme.color}; + box-sizing: border-box; + border-radius: 4px; + transform: rotate(90deg); + } + ::-webkit-scrollbar-thumb { + height: 8px; + background: ${({ theme }) => theme && theme.background}; + border-radius: 4px; + transform: rotate(90deg); + } + .item { + cursor: pointer; + margin-right: 15px; + width: 40px; + height: 45px; + &:last-child { + margin-right: 18px; + } + } + } + & > .add-file { + margin-left: auto; + margin-top: 6px; + display: flex; + label { + width: 34px; + height: 34px; + cursor: pointer; + & > .img { + width: 34px; + height: 34px; + } + } + } + } +`; diff --git a/ui/src/app/components/Job/EditJob/budget/index.tsx b/ui/src/app/components/Job/EditJob/budget/index.tsx new file mode 100644 index 0000000..58a0d80 --- /dev/null +++ b/ui/src/app/components/Job/EditJob/budget/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import Input from '../../../shared/Input/input.commponent'; +import * as Style from './styles'; + +const Budget = ({ handleBlur, budget, setBudget, errors, touched }) => { + return ( + + Budget + setBudget(e.target.value)} + onBlur={handleBlur} + value={budget} + errors={errors} + touched={touched} + style={{ width: '115px', height: '56px', fontSize: '20px' }} + /> + + ); +}; + +export default Budget; diff --git a/ui/src/app/components/Job/EditJob/budget/styles.ts b/ui/src/app/components/Job/EditJob/budget/styles.ts new file mode 100644 index 0000000..625d910 --- /dev/null +++ b/ui/src/app/components/Job/EditJob/budget/styles.ts @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +export const Budget = styled.div` + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + & > .input-budget { + width: 115px; + height: 46px; + } + & > .text { + margin-bottom: 5px; + text-align: center; + font-family: 'Roboto', sans-serif; + font-style: normal; + font-weight: bold; + font-size: 12px; + line-height: 20px; + color: #ffffff; + } +`; diff --git a/ui/src/app/components/Job/EditJob/contacts/index.tsx b/ui/src/app/components/Job/EditJob/contacts/index.tsx new file mode 100644 index 0000000..012b60e --- /dev/null +++ b/ui/src/app/components/Job/EditJob/contacts/index.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import * as Style from './styles'; +import Input from '../../../shared/Input/input.commponent'; +import { contactsIcons } from '../../../../../img'; +import withTheme from '../../../../../HOC/withTheme'; + +const Contacts = ({ + handleChange, + handleBlur, + additional_contacts, + errors, + touched, + ...props +}) => { + return ( + + How to contact +
+
+ phone + +3809754333 +
+
+ mail + vincent@gmail.com +
+
+
+ additional +
+ +
+
+
+ ); +}; + +export default withTheme(Contacts); diff --git a/ui/src/app/components/Job/EditJob/contacts/styles.ts b/ui/src/app/components/Job/EditJob/contacts/styles.ts new file mode 100644 index 0000000..27a700b --- /dev/null +++ b/ui/src/app/components/Job/EditJob/contacts/styles.ts @@ -0,0 +1,51 @@ +import { IPropsTheme } from './../../../../../types/index'; +import styled from 'styled-components'; + +export const Contacts = styled.div` + margin: 0 auto; + font-family: 'Roboto', sans-serif; + font-style: normal; + margin-top: 33px; + display: flex; + flex-direction: column; + align-items: stretch; + & > .title { + font-weight: bold; + font-size: 20px; + line-height: 33px; + text-align: center; + color: #ffffff; + margin-bottom: 24px; + } + & > .profile-contacts { + display: flex; + margin-bottom: 13px; + & > .item { + display: flex; + margin-right: 24px; + &:last-child { + margin-right: 0px; + } + & > .icon { + margin-right: 14px; + } + & > .content { + font-weight: normal; + font-size: 12px; + line-height: 20px; + color: ${({ theme }) => theme && theme.color}; + } + } + } + & > .additional-contact { + width: 100%; + display: flex; + & > .icon { + margin-top: 5px; + margin-right: 14px; + } + & > .input-contact { + width: 100%; + } + } +`; diff --git a/ui/src/app/components/Job/EditJob/description/index.tsx b/ui/src/app/components/Job/EditJob/description/index.tsx new file mode 100644 index 0000000..5236121 --- /dev/null +++ b/ui/src/app/components/Job/EditJob/description/index.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import * as Style from './styles'; +import Textarea from '../../../shared/Textarea/textarea.component'; + +const Description = ({ + handleChange, + handleBlur, + description, + errors, + touched, +}) => { + return ( + + About the Job +
+