diff --git a/src/App.tsx b/src/App.tsx index e34dd1a2..6cfed2f6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,22 +9,23 @@ import AdminPayments from "src/pages/Payments"; import Create from "src/pages/Popups/Create"; import List from "src/pages/Popups/List"; import Update from "src/pages/Popups/Update"; -import NewPreselectionForm from "src/pages/preselectionForms/new"; import EditPreselectionForm from "src/pages/preselectionForms/edit"; +import NewPreselectionForm from "src/pages/preselectionForms/new"; import { setupStore } from "src/store"; import { PageTemplate } from "./features/PageTemplate"; import SentryWrapper from "./features/SentryWrapper"; -import Prospect from "./pages/Prospect"; -import UxDashboard from "./pages/UxDashboard"; import AgreementsList from "./pages/agreements/list"; import SingleAgreementNew from "./pages/agreements/new"; import SingleAgreementEdit from "./pages/agreements/view-edit"; import Campaigns from "./pages/campaigns"; +import EditCampaign from "./pages/campaigns/edit"; import NewCampaign from "./pages/campaigns/new"; import NewCampaignSuccess from "./pages/campaigns/new/Success"; -import EditCampaign from "./pages/campaigns/edit"; -import CampaignPreselectionList from "./pages/preselectionForms"; +import QuotesPage from "./pages/campaigns/quote"; import SelectionPage from "./pages/campaigns/selection"; +import CampaignPreselectionList from "./pages/preselectionForms"; +import Prospect from "./pages/Prospect"; +import UxDashboard from "./pages/UxDashboard"; const SentryRoute = Sentry.withSentryRouting(Route); const history = createBrowserHistory(); @@ -73,6 +74,10 @@ function App() { path="/backoffice/campaigns/:id/edit" component={EditCampaign} /> + { + const { data, isLoading } = useGetCampaignsQuery({ + fields: "id,name", + }); + + if (isLoading) { + return { + data: (offset: number, search?: string) => + Promise.resolve({ results: [], more: false }), + isLoading, + hasValue: (value: SelectOptionType) => false, + }; + } + const campaigns = data?.items || []; + const validCampaigns = (campaigns.filter((c) => c.id && c.name) || []) as { + id: number; + name: string; + }[]; + const options = validCampaigns.map((c) => ({ + label: `CP${c.id.toString()} - ${c.name}`, + value: c.id.toString(), + })); + + return { + data: async ( + pageNumber: number, + value: SelectOptionType, + search?: string + ) => { + const filteredOptions = options.filter( + (o) => + o.label.toLowerCase().includes((search || "").toLowerCase()) && + o.value !== value.value + ); + const results = filteredOptions.slice( + pageNumber * 10, + (pageNumber + 1) * 10 + ); + let more = results.length === 10; + if (pageNumber === 0) { + const selectedOption = options.find((o) => o.value === value.value); + if (selectedOption) { + results.unshift(selectedOption); + more = results.length === 11; + } + } + return { + results, + more, + }; + }, + isLoading, + hasValue: (value: SelectOptionType) => + options.some((o) => o.value === value.value), + }; +}; + +export const CampaignSelect = ({ + name, + label, + onChange, + placeholder, + onBlur, +}: { + name: string; + label: string; + onChange?: (value: SelectType.Option) => void; + placeholder: string; + onBlur?: () => void; +}) => { + const { data, isLoading, hasValue } = useSelectOptions(); + + const [selectedCampaign, setSelectedCampaign] = useState({ + label: "", + value: "", + }); + + const selectedValueIsPresent = + selectedCampaign && selectedCampaign.value !== "" + ? hasValue(selectedCampaign) + : true; + return ( + <> + { + if (value === null) { + value = { label: "", value: "" }; + } + setSelectedCampaign(value); + if (onChange) { + onChange(value); + } + }} + placeholder={placeholder} + menuTargetQuery={"body"} + options={async (offset, search) => { + const options = await data(offset, selectedCampaign as any, search); + return { + ...options, + results: selectedValueIsPresent + ? options.results + : [...options.results, selectedCampaign], + }; + }} + value={selectedCampaign} + /> + > + ); +}; diff --git a/src/pages/campaigns/components/campaignForm/ImportPages/index.tsx b/src/pages/campaigns/components/campaignForm/ImportPages/index.tsx new file mode 100644 index 00000000..df1ab565 --- /dev/null +++ b/src/pages/campaigns/components/campaignForm/ImportPages/index.tsx @@ -0,0 +1,314 @@ +import { + Button, + ErrorMessageWrapper, + FieldProps, + Formik, + FormikField, + Modal, + Select, +} from "@appquality/appquality-design-system"; +import { useFormikContext } from "formik"; +import { useState } from "react"; +import { + usePostDossiersByCampaignManualMutation, + usePostDossiersByCampaignPreviewMutation, +} from "src/services/tryberApi"; +import * as yup from "yup"; +import { FormProps } from ".."; +import { NewCampaignValues } from "../FormProvider"; +import { CampaignSelect } from "./CampaignSelect"; + +type ImportPagesValues = ( + | { + previewAction: "generate" | "no-action"; + previewCampaign: undefined; + } + | { + previewAction: "import"; + previewCampaign: number; + } +) & + ( + | { + manualAction: "generate" | "no-action"; + manualCampaign: undefined; + } + | { + manualAction: "import"; + manualCampaign: number; + } + ); + +const ImportPagesModal = ({ onClose }: { onClose: () => void }) => { + const { + values, + setFieldValue, + submitForm, + setFieldTouched, + errors, + validateForm, + isSubmitting, + } = useFormikContext(); + + return ( + + + { + setFieldTouched("previewAction"); + }} + label="Preview" + value={ + values?.previewAction + ? { label: "", value: values?.previewAction } + : { label: "Nessuna azione", value: "no-action" } + } + options={[ + { label: "Genera dal tipo", value: "generate" }, + { label: "Importa da un'altra campagna", value: "import" }, + { label: "Nessuna azione", value: "no-action" }, + ]} + menuTargetQuery={"body"} + onChange={(option) => { + if (option === null || option.value === "no-action") { + setFieldValue("previewAction", "no-action"); + setFieldValue("previewCampaign", undefined); + return; + } + + setFieldValue("previewAction", option.value); + }} + /> + {values?.previewAction === "import" && ( + + {({ field, form }: FieldProps) => ( + + { + form.setFieldTouched("previewCampaign"); + }} + name={field.name} + label="Campagna da cui importare la preview" + placeholder="Seleziona la campagna da cui importare la preview" + onChange={(value) => { + if (value === null || value.value === "") { + form.setFieldValue("previewCampaign", undefined); + return; + } + form.setFieldValue("previewCampaign", Number(value.value)); + }} + /> + {errors.previewCampaign && ( + + {errors.previewCampaign} + + )} + + )} + + )} + + + { + setFieldTouched("manualAction"); + }} + value={ + values?.manualAction + ? { label: "", value: values?.manualAction } + : { label: "Nessuna azione", value: "no-action" } + } + options={[ + { label: "Genera dal tipo", value: "generate" }, + { label: "Importa da un'altra campagna", value: "import" }, + { label: "Nessuna azione", value: "no-action" }, + ]} + menuTargetQuery={"body"} + onChange={(option) => { + if (option === null || option.value === "no-action") { + setFieldValue("manualAction", "no-action"); + setFieldValue("manualCampaign", undefined); + return; + } + setFieldValue("manualAction", option.value); + }} + /> + {values?.manualAction === "import" && ( + + { + setFieldTouched("manualCampaign"); + }} + label="Campagna da cui importare il manuale" + placeholder="Seleziona la campagna da cui importare il manuale" + onChange={(value) => { + if (value === null || value.value === "") { + setFieldValue("manualCampaign", undefined); + return; + } + setFieldValue("manualCampaign", Number(value.value)); + }} + /> + {errors.manualCampaign && ( + {errors.manualCampaign} + )} + + )} + + + { + onClose(); + }} + > + Annulla + + { + validateForm().then((errors) => { + if (Object.keys(errors).length > 0) { + return; + } + submitForm(); + }); + }} + > + Conferma + + + + ); +}; + +const Confirmation = ({ onClose }: { onClose: () => void }) => { + return ( + + + Hai importato/generato le campagne correttamente + + + Chiudi + + + ); +}; + +const ImportPages = ({ dossier }: { dossier: FormProps["dossier"] }) => { + const [open, setOpen] = useState(false); + const [confirmation, setConfirmation] = useState(false); + const { values } = useFormikContext(); + const [importManual] = usePostDossiersByCampaignManualMutation(); + const [importPreview] = usePostDossiersByCampaignPreviewMutation(); + + if (!values.isEdit || !dossier) return null; + return ( + <> + + initialValues={{ + previewAction: "no-action", + previewCampaign: undefined, + manualAction: "no-action", + manualCampaign: undefined, + }} + validationSchema={yup.object().shape({ + previewAction: yup.string().required("Required"), + previewCampaign: yup.number().when("previewAction", { + is: "import", + then: yup.number().required("Required"), + }), + manualAction: yup.string().required("Required"), + manualCampaign: yup.number().when("manualAction", { + is: "import", + then: yup.number().required("Required"), + }), + })} + validateOnChange={false} + validateOnBlur={false} + onSubmit={async (values, actions) => { + actions.setSubmitting(true); + if (values.previewAction === "import") { + await importPreview({ + campaign: dossier.id.toString(), + body: { + importFrom: values.previewCampaign, + }, + }); + } + if (values.manualAction === "import") { + await importManual({ + campaign: dossier.id.toString(), + body: { + importFrom: values.manualCampaign, + }, + }); + } + + if ( + values.previewAction === "generate" || + values.manualAction === "generate" + ) { + await fetch( + `/wp-json/appq/v1/regenerate-campaign-pages/${dossier.id}` + ); + } + if ( + values.previewAction === "no-action" && + values.manualAction === "no-action" + ) { + setOpen(false); + } else { + setConfirmation(true); + } + actions.resetForm(); + actions.setSubmitting(false); + }} + > + { + setOpen(false); + setConfirmation(false); + }} + size="mid" + > + {confirmation ? ( + { + setOpen(false); + setConfirmation(false); + }} + /> + ) : ( + { + setOpen(false); + setConfirmation(false); + }} + /> + )} + + + setOpen(true)}> + Open + + > + ); +}; + +export default ImportPages; diff --git a/src/pages/campaigns/components/campaignForm/index.tsx b/src/pages/campaigns/components/campaignForm/index.tsx index e58dd6f5..e4749daf 100644 --- a/src/pages/campaigns/components/campaignForm/index.tsx +++ b/src/pages/campaigns/components/campaignForm/index.tsx @@ -18,32 +18,33 @@ import { } from "src/services/tryberApi"; import { styled } from "styled-components"; import { PhaseSelector } from "../PhaseSelector"; -import FocusError from "./FocusError"; -import FormProvider, { NewCampaignValues } from "./FormProvider"; -import { Section } from "./Section"; -import { Stepper } from "./Stepper"; import { CampaignFormContext } from "./campaignFormContext"; import FormOverlay from "./feedbackMessages/FormOverlay"; import BrowsersMultiselect from "./fields/BrowsersMultiselect"; import CountrySelect from "./fields/CountrySelect"; import CustomerSelect from "./fields/CustomerSelect"; -import { FieldWrapper } from "./fields/FieldWrapper"; -import InputField from "./fields/InputField"; -import LanguageSelect from "./fields/LanguagesSelect"; -import ProductType from "./fields/ProductTypeSelect"; -import TestTypeSelect from "./fields/TestTypeSelect"; import CloseDatePicker from "./fields/dates/CloseDatePicker"; import EndDatePicker from "./fields/dates/EndDatePicker"; import StartDatePicker from "./fields/dates/StartDatePicker"; import DeviceMultiselect from "./fields/device/DeviceMultiselect"; +import { FieldWrapper } from "./fields/FieldWrapper"; +import InputField from "./fields/InputField"; +import LanguageSelect from "./fields/LanguagesSelect"; +import ProductType from "./fields/ProductTypeSelect"; import CsmSelect from "./fields/roles/CsmSelect"; import PmSelect from "./fields/roles/PMSelect"; import ResearcherSelect from "./fields/roles/ResearcherSelect"; import TlSelect from "./fields/roles/TLSelect"; -import { SurveyButton } from "./SurveyButton"; import TargetSize from "./fields/TargetSize"; +import TestTypeSelect from "./fields/TestTypeSelect"; +import FocusError from "./FocusError"; +import FormProvider, { NewCampaignValues } from "./FormProvider"; +import ImportPages from "./ImportPages"; +import { Section } from "./Section"; +import { Stepper } from "./Stepper"; +import { SurveyButton } from "./SurveyButton"; -interface FormProps { +export interface FormProps { dossier?: GetDossiersByCampaignApiResponse; isEdit?: boolean; duplicate?: PostDossiersApiArg["body"]["duplicate"]; @@ -311,6 +312,11 @@ const CampaignFormContent = ({ dossier, isEdit, duplicate }: FormProps) => { + {isEdit && ( + + + + )} diff --git a/src/pages/campaigns/quote/QuoteBanner.tsx b/src/pages/campaigns/quote/QuoteBanner.tsx new file mode 100644 index 00000000..b1a39466 --- /dev/null +++ b/src/pages/campaigns/quote/QuoteBanner.tsx @@ -0,0 +1,40 @@ +import { Text, Title } from "@appquality/appquality-design-system"; +import { styled } from "styled-components"; +import { getQuoteStatusPill } from "./statusPill"; + +const StyledDiv = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + + margin-bottom: 20px; +`; + +export const QuoteBanner = ({ status }: { status: string }) => { + return ( + + + The quote is now: + {getQuoteStatusPill(status)} + + + {status === "pending" && ( + + We've prepared this quote based on the provided template. Please + review it to ensure it is accurate or make any necessary updates. + + If you save it without making changes,{" "} + it will be automatically confirmed without + requiring further action from the client. + + )} + + {status === "proposed" && ( + + We are waiting for the customer to accept the quote. Please update it + responsibly. + + )} + + ); +}; diff --git a/src/pages/campaigns/quote/QuoteInput.tsx b/src/pages/campaigns/quote/QuoteInput.tsx new file mode 100644 index 00000000..ea16b175 --- /dev/null +++ b/src/pages/campaigns/quote/QuoteInput.tsx @@ -0,0 +1,94 @@ +import { + Button, + Card, + FormLabel, + Input, +} from "@appquality/appquality-design-system"; +import { useEffect, useState } from "react"; +import siteWideMessageStore from "src/redux/siteWideMessages"; +import { + usePatchDossiersByCampaignQuotationsAndQuoteMutation, + usePostDossiersByCampaignQuotationsMutation, +} from "src/services/tryberApi"; +import { QuoteBanner } from "./QuoteBanner"; +import { useQuoteRecap } from "./useQuoteRecap"; + +export const QuoteInput = ({ campaignId }: { campaignId: string }) => { + const { data, isLoading } = useQuoteRecap({ campaign: Number(campaignId) }); + const [value, setValue] = useState(""); + const { add } = siteWideMessageStore(); + const [createQuote] = usePostDossiersByCampaignQuotationsMutation(); + const [updateQuote] = usePatchDossiersByCampaignQuotationsAndQuoteMutation(); + const [currentCampaign, setCurrentCampaign] = + useState<(typeof data.thisCampaign)[number]>(); + + useEffect(() => { + const [currentCampaign] = data.thisCampaign; + setCurrentCampaign(currentCampaign); + }, [data.thisCampaign]); + + useEffect(() => { + if (currentCampaign) setValue(currentCampaign?.amount); + }, [currentCampaign]); + + const isDisabled = + currentCampaign && + !["pending", "proposed"].includes(currentCampaign?.quoteStatus || ""); + + if (isLoading) return null; + + return ( + + {currentCampaign && } + + + + setValue(e)} + disabled={isDisabled} + /> + + {!isDisabled && ( + { + try { + if (currentCampaign) { + await updateQuote({ + campaign: campaignId, + quote: currentCampaign.quoteId.toString(), + body: { + amount: value, + }, + }).unwrap(); + } else { + await createQuote({ + campaign: campaignId, + body: { + quote: value, + }, + }).unwrap(); + } + + add({ + type: "success", + message: "The quote was successfully sent!", + }); + } catch (e) { + console.error("Cannot add/edit cp quote", e); + add({ + type: "danger", + message: "There was an error sending the quote", + }); + } + }} + > + Send quote + + )} + + ); +}; diff --git a/src/pages/campaigns/quote/QuoteRecap.tsx b/src/pages/campaigns/quote/QuoteRecap.tsx new file mode 100644 index 00000000..b75ff577 --- /dev/null +++ b/src/pages/campaigns/quote/QuoteRecap.tsx @@ -0,0 +1,69 @@ +import { Button, Table } from "@appquality/appquality-design-system"; +import React from "react"; +import openInWordpress from "src/utils/openInWordpress"; +import { getQuoteStatusPill } from "./statusPill"; + +export const QuoteTable = ({ + data, +}: { + data: { + campaignId: number; + campaign: string; + phase: string; + amount: string; + quoteStatus: string; + }[]; +}) => { + return ( + ({ + campaign: { + title: d.campaign, + content: ( + + openInWordpress(e, "open-edit", { id: d.campaignId }) + } + style={{ padding: 0 }} + > + {d.campaign} + + ), + }, + phase: d.phase, + amount: d.amount, + quoteStatus: { + title: d.quoteStatus, + content: getQuoteStatusPill(d.quoteStatus), + }, + key: d.campaignId, + })) || [] + } + columns={[ + { + title: "Campaign", + dataIndex: "campaign", + key: "campaign", + }, + { + title: "Phase", + dataIndex: "phase", + key: "phase", + }, + { + title: "Amount", + dataIndex: "amount", + key: "amount", + }, + { + title: "Quote status", + dataIndex: "quoteStatus", + key: "quoteStatus", + }, + ]} + /> + ); +}; diff --git a/src/pages/campaigns/quote/index.tsx b/src/pages/campaigns/quote/index.tsx new file mode 100644 index 00000000..f55b05ff --- /dev/null +++ b/src/pages/campaigns/quote/index.tsx @@ -0,0 +1,66 @@ +import { BSCol, BSGrid, Card } from "@appquality/appquality-design-system"; +import { useParams } from "react-router-dom"; +import { PageTemplate } from "src/features/PageTemplate"; +import { styled } from "styled-components"; +import { Section } from "../components/campaignForm/Section"; +import { QuoteInput } from "./QuoteInput"; +import { QuoteTable } from "./QuoteRecap"; +import { useQuoteRecap } from "./useQuoteRecap"; + +const FullGrid = styled(BSGrid)` + width: 100%; +`; + +const StickyContainer = styled.div` + @media (min-width: ${(p) => p.theme.grid.breakpoints.lg}) { + position: sticky; + top: 0; + } +`; + +const EditCampaign = () => { + const { id } = useParams<{ id: string }>(); + const { data } = useQuoteRecap({ campaign: Number(id) }); + + return ( + + + + + {data.history.length > 0 && ( + + + + )} + + {data.otherCampaigns.length > 0 ? ( + + ) : ( + This is the first quote for this workspace. + )} + + + + + + + + + + + + + ); +}; + +export default EditCampaign; diff --git a/src/pages/campaigns/quote/statusPill.tsx b/src/pages/campaigns/quote/statusPill.tsx new file mode 100644 index 00000000..d8a5b2f4 --- /dev/null +++ b/src/pages/campaigns/quote/statusPill.tsx @@ -0,0 +1,16 @@ +import { Pill } from "@appquality/appquality-design-system"; + +export const getQuoteStatusPill = ( + quoteStatus: string // "pending" | "proposed" | "approved" | "rejected" +) => { + switch (quoteStatus) { + case "proposed": + return Proposed; + case "approved": + return Approved; + case "rejected": + return Rejected; + default: + return Estimated; + } +}; diff --git a/src/pages/campaigns/quote/useQuoteRecap.tsx b/src/pages/campaigns/quote/useQuoteRecap.tsx new file mode 100644 index 00000000..4d13b4f1 --- /dev/null +++ b/src/pages/campaigns/quote/useQuoteRecap.tsx @@ -0,0 +1,81 @@ +import { useMemo } from "react"; +import { + useGetCampaignsQuery, + useGetDossiersByCampaignQuery, + useGetDossiersByCampaignQuotesHistoryQuery, +} from "src/services/tryberApi"; + +const mapData = < + T extends { + id?: number; + name?: string; + phase?: { name: string }; + quote?: { id: number; price: string; status: string }; + } +>( + x: T[] +) => + x.map((y) => ({ + campaignId: y.id || 0, + campaign: `CP${y.id} - ${y.name}`, + phase: y.phase?.name || "unknown", + amount: y.quote?.price || "unknown", + quoteId: y.quote?.id || 0, + quoteStatus: y.quote?.status || "unknown", + })); + +export const useQuoteRecap = ({ campaign }: { campaign: number }) => { + const { data: dossier } = useGetDossiersByCampaignQuery({ + campaign: campaign.toString(), + }); + const { data: historyData } = useGetDossiersByCampaignQuotesHistoryQuery({ + campaign: campaign.toString(), + }); + + const historyItems = (historyData?.items || []).map((h) => ({ + id: h.campaign.id, + name: h.campaign.title, + phase: { name: h.campaign.phase_name }, + quote: { + id: h.quote.id, + price: h.quote.amount, + status: h.quote.status, + }, + })); + + const { data, isLoading, isError } = useGetCampaignsQuery( + { + fields: "id,name,quote,phase", + filterBy: { hasQuote: 1, customer: dossier?.customer?.id }, + }, + { + skip: !dossier, + } + ); + + const { thisCampaign, history, otherCampaigns } = useMemo(() => { + if (!data || !data.items) { + return { + thisCampaign: mapData([]), + history: mapData([]), + otherCampaigns: mapData([]), + }; + } + + return { + thisCampaign: mapData(data.items.filter((c) => c.id === campaign)), + history: mapData(historyItems), + otherCampaigns: mapData(data.items.filter((c) => c.id !== campaign)), + }; + }, [data, campaign]); + + return { + data: { + thisCampaign, + history, + otherCampaigns, + }, + isLoading, + isError, + }; +}; diff --git a/src/services/tryberApi/api.ts b/src/services/tryberApi/api.ts index 062299b5..76a12a10 100644 --- a/src/services/tryberApi/api.ts +++ b/src/services/tryberApi/api.ts @@ -36,6 +36,7 @@ export const api = createApi({ "UX", "Selection", "CampaignForms", + "Quote", ], endpoints: () => ({}), // auto generated npm run generate-api }); diff --git a/src/services/tryberApi/apiTags.ts b/src/services/tryberApi/apiTags.ts index a556422a..309cf87e 100644 --- a/src/services/tryberApi/apiTags.ts +++ b/src/services/tryberApi/apiTags.ts @@ -9,7 +9,7 @@ tryberApi.enhanceEndpoints({ invalidatesTags: ["Campaigns"], }, getCampaigns: { - providesTags: ["Campaigns"], + providesTags: ["Campaigns", "Quote"], }, getCampaignsByCampaign: { providesTags: ["Campaigns"], @@ -233,6 +233,15 @@ tryberApi.enhanceEndpoints({ postCustomers: { invalidatesTags: ["Customers"], }, + postDossiersByCampaignQuotations: { + invalidatesTags: ["Quote"], + }, + patchDossiersByCampaignQuotationsAndQuote: { + invalidatesTags: ["Quote"], + }, + getDossiersByCampaignQuotesHistory: { + providesTags: ["Quote"], + }, }, }); diff --git a/src/services/tryberApi/index.ts b/src/services/tryberApi/index.ts index ecf6277d..d609fe60 100644 --- a/src/services/tryberApi/index.ts +++ b/src/services/tryberApi/index.ts @@ -59,6 +59,9 @@ const injectedRtkApi = api.injectEndpoints({ body: queryArg.body, }), }), + getBrowsers: build.query({ + query: () => ({ url: `/browsers` }), + }), postCampaigns: build.mutation< PostCampaignsApiResponse, PostCampaignsApiArg @@ -359,6 +362,24 @@ const injectedRtkApi = api.injectEndpoints({ body: queryArg.body, }), }), + getCustomersByCustomerProjects: build.query< + GetCustomersByCustomerProjectsApiResponse, + GetCustomersByCustomerProjectsApiArg + >({ + query: (queryArg) => ({ + url: `/customers/${queryArg.customer}/projects`, + }), + }), + postCustomersByCustomerProjects: build.mutation< + PostCustomersByCustomerProjectsApiResponse, + PostCustomersByCustomerProjectsApiArg + >({ + query: (queryArg) => ({ + url: `/customers/${queryArg.customer}/projects`, + method: "POST", + body: queryArg.body, + }), + }), getCustomUserFields: build.query< GetCustomUserFieldsApiResponse, GetCustomUserFieldsApiArg @@ -392,6 +413,67 @@ const injectedRtkApi = api.injectEndpoints({ params: { filterBy: queryArg.filterBy }, }), }), + postDossiers: build.mutation({ + query: (queryArg) => ({ + url: `/dossiers`, + method: "POST", + body: queryArg.body, + }), + }), + putDossiersByCampaign: build.mutation< + PutDossiersByCampaignApiResponse, + PutDossiersByCampaignApiArg + >({ + query: (queryArg) => ({ + url: `/dossiers/${queryArg.campaign}`, + method: "PUT", + body: queryArg.dossierCreationData, + }), + }), + getDossiersByCampaign: build.query< + GetDossiersByCampaignApiResponse, + GetDossiersByCampaignApiArg + >({ + query: (queryArg) => ({ url: `/dossiers/${queryArg.campaign}` }), + }), + putDossiersByCampaignPhases: build.mutation< + PutDossiersByCampaignPhasesApiResponse, + PutDossiersByCampaignPhasesApiArg + >({ + query: (queryArg) => ({ + url: `/dossiers/${queryArg.campaign}/phases`, + method: "PUT", + body: queryArg.body, + }), + }), + postDossiersByCampaignQuotations: build.mutation< + PostDossiersByCampaignQuotationsApiResponse, + PostDossiersByCampaignQuotationsApiArg + >({ + query: (queryArg) => ({ + url: `/dossiers/${queryArg.campaign}/quotations`, + method: "POST", + body: queryArg.body, + }), + }), + patchDossiersByCampaignQuotationsAndQuote: build.mutation< + PatchDossiersByCampaignQuotationsAndQuoteApiResponse, + PatchDossiersByCampaignQuotationsAndQuoteApiArg + >({ + query: (queryArg) => ({ + url: `/dossiers/${queryArg.campaign}/quotations/${queryArg.quote}`, + method: "PATCH", + body: queryArg.body, + }), + }), + getDossiersByCampaignQuotesHistory: build.query< + GetDossiersByCampaignQuotesHistoryApiResponse, + GetDossiersByCampaignQuotesHistoryApiArg + >({ + query: (queryArg) => ({ + url: `/dossiers/${queryArg.campaign}/quotesHistory`, + }), + }), getEducation: build.query({ query: () => ({ url: `/education` }), }), @@ -476,6 +558,9 @@ const injectedRtkApi = api.injectEndpoints({ method: "DELETE", }), }), + getPhases: build.query({ + query: () => ({ url: `/phases` }), + }), getPopups: build.query({ query: (queryArg) => ({ url: `/popups`, @@ -505,6 +590,12 @@ const injectedRtkApi = api.injectEndpoints({ body: queryArg.body, }), }), + getProductTypes: build.query< + GetProductTypesApiResponse, + GetProductTypesApiArg + >({ + query: () => ({ url: `/productTypes` }), + }), getUsers: build.query({ query: () => ({ url: `/users` }), }), @@ -524,6 +615,12 @@ const injectedRtkApi = api.injectEndpoints({ method: "HEAD", }), }), + getUsersByRoleByRole: build.query< + GetUsersByRoleByRoleApiResponse, + GetUsersByRoleByRoleApiArg + >({ + query: (queryArg) => ({ url: `/users/by-role/${queryArg.role}` }), + }), getUsersMe: build.query({ query: (queryArg) => ({ url: `/users/me`, @@ -874,72 +971,23 @@ const injectedRtkApi = api.injectEndpoints({ >({ query: () => ({ url: `/users/me/rank/list` }), }), - postDossiers: build.mutation({ - query: (queryArg) => ({ - url: `/dossiers`, - method: "POST", - body: queryArg.body, - }), - }), - putDossiersByCampaign: build.mutation< - PutDossiersByCampaignApiResponse, - PutDossiersByCampaignApiArg - >({ - query: (queryArg) => ({ - url: `/dossiers/${queryArg.campaign}`, - method: "PUT", - body: queryArg.dossierCreationData, - }), - }), - getDossiersByCampaign: build.query< - GetDossiersByCampaignApiResponse, - GetDossiersByCampaignApiArg - >({ - query: (queryArg) => ({ url: `/dossiers/${queryArg.campaign}` }), - }), - getCustomersByCustomerProjects: build.query< - GetCustomersByCustomerProjectsApiResponse, - GetCustomersByCustomerProjectsApiArg - >({ - query: (queryArg) => ({ - url: `/customers/${queryArg.customer}/projects`, - }), - }), - postCustomersByCustomerProjects: build.mutation< - PostCustomersByCustomerProjectsApiResponse, - PostCustomersByCustomerProjectsApiArg + postDossiersByCampaignManual: build.mutation< + PostDossiersByCampaignManualApiResponse, + PostDossiersByCampaignManualApiArg >({ query: (queryArg) => ({ - url: `/customers/${queryArg.customer}/projects`, + url: `/dossiers/${queryArg.campaign}/manual`, method: "POST", body: queryArg.body, }), }), - getUsersByRoleByRole: build.query< - GetUsersByRoleByRoleApiResponse, - GetUsersByRoleByRoleApiArg - >({ - query: (queryArg) => ({ url: `/users/by-role/${queryArg.role}` }), - }), - getBrowsers: build.query({ - query: () => ({ url: `/browsers` }), - }), - getProductTypes: build.query< - GetProductTypesApiResponse, - GetProductTypesApiArg - >({ - query: () => ({ url: `/productTypes` }), - }), - getPhases: build.query({ - query: () => ({ url: `/phases` }), - }), - putDossiersByCampaignPhases: build.mutation< - PutDossiersByCampaignPhasesApiResponse, - PutDossiersByCampaignPhasesApiArg + postDossiersByCampaignPreview: build.mutation< + PostDossiersByCampaignPreviewApiResponse, + PostDossiersByCampaignPreviewApiArg >({ query: (queryArg) => ({ - url: `/dossiers/${queryArg.campaign}/phases`, - method: "PUT", + url: `/dossiers/${queryArg.campaign}/preview`, + method: "POST", body: queryArg.body, }), }), @@ -1021,6 +1069,13 @@ export type PostAuthenticateApiArg = { password: string; }; }; +export type GetBrowsersApiResponse = /** status 200 OK */ { + results: { + id: number; + name: string; + }[]; +}; +export type GetBrowsersApiArg = void; export type PostCampaignsApiResponse = /** status 201 A single Campaigns with the Campaign id and Project data */ Campaign & { id: number; @@ -1077,6 +1132,11 @@ export type GetCampaignsApiResponse = /** status 200 OK */ { surname: string; }; }[]; + quote?: { + id: number; + price: string; + status: string; + }; }[]; } & PaginationData; export type GetCampaignsApiArg = { @@ -1637,6 +1697,25 @@ export type PostCustomersApiArg = { name: string; }; }; +export type GetCustomersByCustomerProjectsApiResponse = /** status 200 OK */ { + results: { + id: number; + name: string; + }[]; +}; +export type GetCustomersByCustomerProjectsApiArg = { + customer: string; +}; +export type PostCustomersByCustomerProjectsApiResponse = /** status 200 OK */ { + id: number; + name: string; +}; +export type PostCustomersByCustomerProjectsApiArg = { + customer: string; + body: { + name: string; + }; +}; export type GetCustomUserFieldsApiResponse = /** status 200 OK */ { group: { id: number; @@ -1678,58 +1757,212 @@ export type GetDevicesByDeviceTypeOsVersionsApiArg = { /** Key-value Array for item filtering */ filterBy?: object; }; -export type GetEducationApiResponse = /** status 200 OK */ { - id: number; - name: string; -}[]; -export type GetEducationApiArg = void; -export type GetEmploymentsApiResponse = /** status 200 OK */ { - id: number; - name: string; -}[]; -export type GetEmploymentsApiArg = void; -export type PostJotformsByCampaignApiResponse = /** status 200 OK */ {}; -export type PostJotformsByCampaignApiArg = { - /** A campaign id */ - campaign: string; - body: { - formId: string; - testerIdColumn: string; +export type PostDossiersApiResponse = + /** status 201 Created */ + | { + id: number; + message?: "HOOK_FAILED"; + } + | /** status 206 Partial Content */ { + id?: number; + }; +export type PostDossiersApiArg = { + body: DossierCreationData & { + duplicate?: { + fields?: number; + useCases?: number; + mailMerges?: number; + pages?: number; + testers?: number; + campaign?: number; + }; + } & { + skipPagesAndTasks?: number; }; }; -export type GetJotformsFormsApiResponse = /** status 200 OK */ { - id: string; - name: string; - createdAt: string; -}[]; -export type GetJotformsFormsApiArg = void; -export type GetJotformsFormsByFormIdQuestionsApiResponse = - /** status 200 OK */ { - id: string; - name: string; - title: string; - description?: string; - type: string; - }[]; -export type GetJotformsFormsByFormIdQuestionsApiArg = { - formId: string; +export type PutDossiersByCampaignApiResponse = /** status 200 OK */ {}; +export type PutDossiersByCampaignApiArg = { + /** A campaign id */ + campaign: string; + dossierCreationData: DossierCreationData; }; -export type GetLanguagesApiResponse = /** status 200 OK */ { +export type GetDossiersByCampaignApiResponse = /** status 200 OK */ { id: number; - name: string; -}[]; -export type GetLanguagesApiArg = void; -export type GetLevelsApiResponse = /** status 200 OK */ LevelDefinition[]; -export type GetLevelsApiArg = void; -export type PostMediaApiResponse = /** status 200 OK */ { - files: { + title: { + customer: string; + tester: string; + }; + startDate: string; + endDate: string; + closeDate: string; + customer: { + id: number; name: string; - path: string; - }[]; - failed?: { + }; + project: { + id: number; name: string; - errorCode: string; - }[]; + }; + testType: { + id: number; + name: string; + }; + deviceList: { + id: number; + name: string; + }[]; + csm: { + id: number; + name: string; + }; + roles?: { + role?: { + id: number; + name: string; + }; + user?: { + id: number; + name: string; + surname: string; + }; + }[]; + description?: string; + productLink?: string; + goal?: string; + outOfScope?: string; + deviceRequirements?: string; + target?: { + notes?: string; + size?: number; + cap?: number; + }; + countries?: CountryCode[]; + languages?: { + name: string; + }[]; + browsers?: { + id: number; + name: string; + }[]; + productType?: { + id: number; + name: string; + }; + phase: { + id: number; + name: string; + }; + notes?: string; +}; +export type GetDossiersByCampaignApiArg = { + /** A campaign id */ + campaign: string; +}; +export type PutDossiersByCampaignPhasesApiResponse = /** status 200 OK */ { + id: number; + name: string; +}; +export type PutDossiersByCampaignPhasesApiArg = { + /** A campaign id */ + campaign: string; + body: { + phase: number; + }; +}; +export type PostDossiersByCampaignQuotationsApiResponse = + /** status 201 Created */ { + id?: number; + }; +export type PostDossiersByCampaignQuotationsApiArg = { + /** A campaign id */ + campaign: string; + body: { + quote?: string; + notes?: string; + }; +}; +export type PatchDossiersByCampaignQuotationsAndQuoteApiResponse = + /** status 200 OK */ {}; +export type PatchDossiersByCampaignQuotationsAndQuoteApiArg = { + /** A campaign id */ + campaign: string; + quote: string; + body: { + amount?: string; + }; +}; +export type GetDossiersByCampaignQuotesHistoryApiResponse = + /** status 200 OK */ { + items: { + campaign: { + id: number; + title: string; + phase_id: number; + phase_name: string; + }; + quote: { + id: number; + amount: string; + status: "pending" | "proposed" | "approved" | "rejected"; + }; + }[]; + }; +export type GetDossiersByCampaignQuotesHistoryApiArg = { + /** A campaign id */ + campaign: string; +}; +export type GetEducationApiResponse = /** status 200 OK */ { + id: number; + name: string; +}[]; +export type GetEducationApiArg = void; +export type GetEmploymentsApiResponse = /** status 200 OK */ { + id: number; + name: string; +}[]; +export type GetEmploymentsApiArg = void; +export type PostJotformsByCampaignApiResponse = /** status 200 OK */ {}; +export type PostJotformsByCampaignApiArg = { + /** A campaign id */ + campaign: string; + body: { + formId: string; + testerIdColumn: string; + }; +}; +export type GetJotformsFormsApiResponse = /** status 200 OK */ { + id: string; + name: string; + createdAt: string; +}[]; +export type GetJotformsFormsApiArg = void; +export type GetJotformsFormsByFormIdQuestionsApiResponse = + /** status 200 OK */ { + id: string; + name: string; + title: string; + description?: string; + type: string; + }[]; +export type GetJotformsFormsByFormIdQuestionsApiArg = { + formId: string; +}; +export type GetLanguagesApiResponse = /** status 200 OK */ { + id: number; + name: string; +}[]; +export type GetLanguagesApiArg = void; +export type GetLevelsApiResponse = /** status 200 OK */ LevelDefinition[]; +export type GetLevelsApiArg = void; +export type PostMediaApiResponse = /** status 200 OK */ { + files: { + name: string; + path: string; + }[]; + failed?: { + name: string; + errorCode: string; + }[]; }; export type PostMediaApiArg = { body: { @@ -1787,6 +2020,17 @@ export type DeletePaymentsByPaymentIdApiResponse = export type DeletePaymentsByPaymentIdApiArg = { paymentId: string; }; +export type GetPhasesApiResponse = /** status 200 OK */ { + results: { + id: number; + name: string; + type: { + id: number; + name: string; + }; + }[]; +}; +export type GetPhasesApiArg = void; export type GetPopupsApiResponse = /** status 200 OK */ ({ id?: number; } & Popup)[]; @@ -1815,6 +2059,13 @@ export type PatchPopupsByPopupApiArg = { popup: number; body: Popup; }; +export type GetProductTypesApiResponse = /** status 200 OK */ { + results: { + id: number; + name: string; + }[]; +}; +export type GetProductTypesApiArg = void; export type GetUsersApiResponse = /** status 200 OK */ User[]; export type GetUsersApiArg = void; export type PostUsersApiResponse = /** status 201 Created */ { @@ -1835,6 +2086,16 @@ export type HeadUsersByEmailByEmailApiResponse = unknown; export type HeadUsersByEmailByEmailApiArg = { email: string; }; +export type GetUsersByRoleByRoleApiResponse = /** status 200 OK */ { + results: { + id: number; + name: string; + surname: string; + }[]; +}; +export type GetUsersByRoleByRoleApiArg = { + role: "tester_lead" | "quality_leader" | "ux_researcher" | "assistants"; +}; export type GetUsersMeApiResponse = /** status 200 OK */ { username?: string; name?: string; @@ -2075,15 +2336,7 @@ export type PostUsersMeCampaignsByCampaignIdBugsApiResponse = current: string; severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; replicability: "ONCE" | "SOMETIMES" | "ALWAYS"; - type: - | "CRASH" - | "GRAPHIC" - | "MALFUNCTION" - | "OTHER" - | "PERFORMANCE" - | "SECURITY" - | "TYPO" - | "USABILITY"; + type: string; notes: string; usecase: string; device: UserDevice; @@ -2103,15 +2356,7 @@ export type PostUsersMeCampaignsByCampaignIdBugsApiArg = { current: string; severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; replicability: "ONCE" | "SOMETIMES" | "ALWAYS"; - type: - | "CRASH" - | "GRAPHIC" - | "MALFUNCTION" - | "OTHER" - | "PERFORMANCE" - | "SECURITY" - | "TYPO" - | "USABILITY"; + type: string; notes: string; lastSeen: string; usecase: number; @@ -2559,168 +2804,21 @@ export type GetUsersMeRankListApiResponse = /** status 200 OK */ { peers: RankingItem[]; }; export type GetUsersMeRankListApiArg = void; -export type PostDossiersApiResponse = - /** status 201 Created */ - | { - id: number; - message?: "HOOK_FAILED"; - } - | /** status 206 Partial Content */ { - id?: number; - }; -export type PostDossiersApiArg = { - body: DossierCreationData & { - duplicate?: { - fields?: number; - useCases?: number; - mailMerges?: number; - pages?: number; - testers?: number; - campaign?: number; - }; - }; -}; -export type PutDossiersByCampaignApiResponse = /** status 200 OK */ {}; -export type PutDossiersByCampaignApiArg = { +export type PostDossiersByCampaignManualApiResponse = /** status 200 OK */ {}; +export type PostDossiersByCampaignManualApiArg = { /** A campaign id */ campaign: string; - dossierCreationData: DossierCreationData; -}; -export type GetDossiersByCampaignApiResponse = /** status 200 OK */ { - id: number; - title: { - customer: string; - tester: string; - }; - startDate: string; - endDate: string; - closeDate: string; - customer: { - id: number; - name: string; - }; - project: { - id: number; - name: string; - }; - testType: { - id: number; - name: string; - }; - deviceList: { - id: number; - name: string; - }[]; - csm: { - id: number; - name: string; - }; - roles?: { - role?: { - id: number; - name: string; - }; - user?: { - id: number; - name: string; - surname: string; - }; - }[]; - description?: string; - productLink?: string; - goal?: string; - outOfScope?: string; - deviceRequirements?: string; - target?: { - notes?: string; - size?: number; - cap?: number; - }; - countries?: CountryCode[]; - languages?: { - name: string; - }[]; - browsers?: { - id: number; - name: string; - }[]; - productType?: { - id: number; - name: string; - }; - phase: { - id: number; - name: string; - }; - notes?: string; -}; -export type GetDossiersByCampaignApiArg = { - /** A campaign id */ - campaign: string; -}; -export type GetCustomersByCustomerProjectsApiResponse = /** status 200 OK */ { - results: { - id: number; - name: string; - }[]; -}; -export type GetCustomersByCustomerProjectsApiArg = { - customer: string; -}; -export type PostCustomersByCustomerProjectsApiResponse = /** status 200 OK */ { - id: number; - name: string; -}; -export type PostCustomersByCustomerProjectsApiArg = { - customer: string; body: { - name: string; + importFrom: number; }; }; -export type GetUsersByRoleByRoleApiResponse = /** status 200 OK */ { - results: { - id: number; - name: string; - surname: string; - }[]; -}; -export type GetUsersByRoleByRoleApiArg = { - role: "tester_lead" | "quality_leader" | "ux_researcher" | "assistants"; -}; -export type GetBrowsersApiResponse = /** status 200 OK */ { - results: { - id: number; - name: string; - }[]; -}; -export type GetBrowsersApiArg = void; -export type GetProductTypesApiResponse = /** status 200 OK */ { - results: { - id: number; - name: string; - }[]; -}; -export type GetProductTypesApiArg = void; -export type GetPhasesApiResponse = /** status 200 OK */ { - results: { - id: number; - name: string; - type: { - id: number; - name: string; - }; - }[]; -}; -export type GetPhasesApiArg = void; -export type PutDossiersByCampaignPhasesApiResponse = /** status 200 OK */ { - id: number; - name: string; -}; -export type PutDossiersByCampaignPhasesApiArg = { +export type PostDossiersByCampaignPreviewApiResponse = + /** status 200 OK */ object; +export type PostDossiersByCampaignPreviewApiArg = { /** A campaign id */ campaign: string; body: { - phase: number; + importFrom: number; }; }; export type Agreement = { @@ -2882,6 +2980,39 @@ export type CustomUserFieldsData = { format?: string; options?: CustomUserFieldsDataOption[]; }; +export type CountryCode = string; +export type DossierCreationData = { + project: number; + testType: number; + title: { + customer: string; + tester?: string; + }; + startDate: string; + endDate?: string; + closeDate?: string; + deviceList: number[]; + csm?: number; + roles?: { + role: number; + user: number; + }[]; + description?: string; + productLink?: string; + goal?: string; + outOfScope?: string; + deviceRequirements?: string; + target?: { + notes?: string; + size?: number; + cap?: number; + }; + countries?: CountryCode[]; + languages?: string[]; + browsers?: number[]; + productType?: number; + notes?: string; +}; export type LevelDefinition = { id: number; name: string; @@ -2991,39 +3122,6 @@ export type RankingItem = { name: string; monthly_exp: number; }; -export type CountryCode = string; -export type DossierCreationData = { - project: number; - testType: number; - title: { - customer: string; - tester?: string; - }; - startDate: string; - endDate?: string; - closeDate?: string; - deviceList: number[]; - csm?: number; - roles?: { - role: number; - user: number; - }[]; - description?: string; - productLink?: string; - goal?: string; - outOfScope?: string; - deviceRequirements?: string; - target?: { - notes?: string; - size?: number; - cap?: number; - }; - countries?: CountryCode[]; - languages?: string[]; - browsers?: number[]; - productType?: number; - notes?: string; -}; export const { useGetQuery, useGetAgreementsQuery, @@ -3032,6 +3130,7 @@ export const { useDeleteAgreementsByAgreementIdMutation, useGetAgreementsByAgreementIdQuery, usePostAuthenticateMutation, + useGetBrowsersQuery, usePostCampaignsMutation, useGetCampaignsQuery, useGetCampaignsByCampaignQuery, @@ -3065,10 +3164,19 @@ export const { useGetCountriesByCodeRegionQuery, useGetCustomersQuery, usePostCustomersMutation, + useGetCustomersByCustomerProjectsQuery, + usePostCustomersByCustomerProjectsMutation, useGetCustomUserFieldsQuery, useGetDevicesByDeviceTypeModelsQuery, useGetDevicesByDeviceTypeOperatingSystemsQuery, useGetDevicesByDeviceTypeOsVersionsQuery, + usePostDossiersMutation, + usePutDossiersByCampaignMutation, + useGetDossiersByCampaignQuery, + usePutDossiersByCampaignPhasesMutation, + usePostDossiersByCampaignQuotationsMutation, + usePatchDossiersByCampaignQuotationsAndQuoteMutation, + useGetDossiersByCampaignQuotesHistoryQuery, useGetEducationQuery, useGetEmploymentsQuery, usePostJotformsByCampaignMutation, @@ -3081,13 +3189,16 @@ export const { useGetPaymentsQuery, usePostPaymentsByPaymentIdMutation, useDeletePaymentsByPaymentIdMutation, + useGetPhasesQuery, useGetPopupsQuery, usePostPopupsMutation, useGetPopupsByPopupQuery, usePatchPopupsByPopupMutation, + useGetProductTypesQuery, useGetUsersQuery, usePostUsersMutation, useHeadUsersByEmailByEmailMutation, + useGetUsersByRoleByRoleQuery, useGetUsersMeQuery, usePutUsersMeMutation, usePatchUsersMeMutation, @@ -3125,14 +3236,6 @@ export const { useGetUsersMePopupsByPopupQuery, useGetUsersMeRankQuery, useGetUsersMeRankListQuery, - usePostDossiersMutation, - usePutDossiersByCampaignMutation, - useGetDossiersByCampaignQuery, - useGetCustomersByCustomerProjectsQuery, - usePostCustomersByCustomerProjectsMutation, - useGetUsersByRoleByRoleQuery, - useGetBrowsersQuery, - useGetProductTypesQuery, - useGetPhasesQuery, - usePutDossiersByCampaignPhasesMutation, + usePostDossiersByCampaignManualMutation, + usePostDossiersByCampaignPreviewMutation, } = injectedRtkApi;