Merge branch 'main' into CALCOM-11196

CALCOM-11196
GitStart-Cal.com 2023-09-27 11:49:52 +00:00 committed by GitHub
commit 7b909fd23b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 156 additions and 46 deletions

View File

@ -10,9 +10,9 @@ import { stringOrNumber } from "@calcom/prisma/zod-utils";
/**
* @swagger
* /availability:
* /teams/{teamId}/availability:
* get:
* summary: Find user or team availability
* summary: Find team availability
* parameters:
* - in: query
* name: apiKey
@ -21,25 +21,14 @@ import { stringOrNumber } from "@calcom/prisma/zod-utils";
* type: string
* example: "1234abcd5678efgh"
* description: Your API key
* - in: query
* name: userId
* schema:
* type: integer
* example: 101
* description: ID of the user to fetch the availability for
* - in: query
* - in: path
* name: teamId
* required: true
* schema:
* type: integer
* example: 123
* description: ID of the team to fetch the availability for
* - in: query
* name: username
* schema:
* type: string
* example: "alice"
* description: username of the user to fetch the availability for
* - in: query
* name: dateFrom
* schema:
* type: string
@ -59,7 +48,7 @@ import { stringOrNumber } from "@calcom/prisma/zod-utils";
* type: integer
* example: 123
* description: Event Type ID of the event type to fetch the availability for
* operationId: availability
* operationId: team-availability
* tags:
* - availability
* responses:
@ -96,7 +85,89 @@ import { stringOrNumber } from "@calcom/prisma/zod-utils";
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: User not found | Team not found | Team has no members
* description: Team not found | Team has no members
*
* /availability:
* get:
* summary: Find user availability
* parameters:
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* example: "1234abcd5678efgh"
* description: Your API key
* - in: query
* name: userId
* schema:
* type: integer
* example: 101
* description: ID of the user to fetch the availability for
* - in: query
* name: username
* schema:
* type: string
* example: "alice"
* description: username of the user to fetch the availability for
* - in: query
* name: dateFrom
* schema:
* type: string
* format: date
* example: "2023-05-14 00:00:00"
* description: Start Date of the availability query
* - in: query
* name: dateTo
* schema:
* type: string
* format: date
* example: "2023-05-20 00:00:00"
* description: End Date of the availability query
* - in: query
* name: eventTypeId
* schema:
* type: integer
* example: 123
* description: Event Type ID of the event type to fetch the availability for
* operationId: user-availability
* tags:
* - availability
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* type: object
* example:
* busy:
* - start: "2023-05-14T10:00:00.000Z"
* end: "2023-05-14T11:00:00.000Z"
* title: "Team meeting between Alice and Bob"
* - start: "2023-05-15T14:00:00.000Z"
* end: "2023-05-15T15:00:00.000Z"
* title: "Project review between Carol and Dave"
* - start: "2023-05-16T09:00:00.000Z"
* end: "2023-05-16T10:00:00.000Z"
* - start: "2023-05-17T13:00:00.000Z"
* end: "2023-05-17T14:00:00.000Z"
* timeZone: "America/New_York"
* workingHours:
* - days: [1, 2, 3, 4, 5]
* startTime: 540
* endTime: 1020
* userId: 101
* dateOverrides:
* - date: "2023-05-15"
* startTime: 600
* endTime: 960
* userId: 101
* currentSeats: 4
* 401:
* description: Authorization information is missing or invalid.
* 404:
* description: User not found
*/
interface MemberRoles {
[userId: number | string]: MembershipRole;

View File

@ -18,7 +18,7 @@ import {
Skeleton,
TextField,
} from "@calcom/ui";
import { Plus, FileText, X } from "@calcom/ui/components/icon";
import { Plus, FileText, X, ArrowUp, ArrowDown } from "@calcom/ui/components/icon";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
@ -29,7 +29,7 @@ import SingleForm, {
export { getServerSideProps };
type HookForm = UseFormReturn<RoutingFormWithResponseCount>;
type SelectOption = { placeholder: string; value: string };
type SelectOption = { placeholder: string; value: string; id: string };
export const FieldTypes = [
{
@ -88,12 +88,13 @@ function Field({
appUrl: string;
}) {
const { t } = useLocale();
const [animationRef] = useAutoAnimate<HTMLUListElement>();
const [options, setOptions] = useState<SelectOption[]>([
{ placeholder: "< 10", value: "" },
{ placeholder: "10-100", value: "" },
{ placeholder: "100-500", value: "" },
{ placeholder: "> 500", value: "" },
{ placeholder: "< 10", value: "", id: uuidv4() },
{ placeholder: "10-100", value: "", id: uuidv4() },
{ placeholder: "100-500", value: "", id: uuidv4() },
{ placeholder: "> 500", value: "", id: uuidv4() },
]);
const handleRemoveOptions = (index: number) => {
@ -108,6 +109,7 @@ function Field({
{
placeholder: "New Option",
value: "",
id: uuidv4(),
},
]);
};
@ -118,6 +120,7 @@ function Field({
const values: SelectOption[] = originalValues.split("\n").map((fieldValue) => ({
value: fieldValue,
placeholder: "",
id: uuidv4(),
}));
setOptions(values);
}
@ -132,6 +135,7 @@ function Field({
...opt,
...(index === optionIndex ? { value: e.target.value } : {}),
}));
setOptions(updatedOptions);
updateSelectText(updatedOptions);
};
@ -156,6 +160,19 @@ function Field({
name: `${hookFieldNamespace}.identifier`,
});
function move(index: number, increment: 1 | -1) {
const newList = [...options];
const type = options[index];
const tmp = options[index + increment];
if (tmp) {
newList[index] = tmp;
newList[index + increment] = type;
}
setOptions(newList);
updateSelectText(newList);
}
return (
<div
data-testid="field"
@ -242,29 +259,51 @@ function Field({
<Skeleton as={Label} loadingClassName="w-16" title={t("Options")}>
{t("options")}
</Skeleton>
{options.map((field, index) => (
<div key={`select-option-${index}`}>
<TextField
disabled={!!router}
containerClassName="[&>*:first-child]:border [&>*:first-child]:border-default hover:[&>*:first-child]:border-gray-400"
className="border-0 focus:ring-0 focus:ring-offset-0"
labelSrOnly
placeholder={field.placeholder.toString()}
value={field.value}
type="text"
addOnClassname="bg-transparent border-0"
onChange={(e) => handleChange(e, index)}
addOnSuffix={
<button
type="button"
onClick={() => handleRemoveOptions(index)}
aria-label={t("remove")}>
<X className="h-4 w-4" />
</button>
}
/>
</div>
))}
<ul ref={animationRef}>
{options.map((field, index) => (
<li key={`select-option-${field.id}`} className="group mt-2 flex items-center gap-2">
<div className="flex flex-col gap-2">
{options.length && index !== 0 ? (
<button
type="button"
onClick={() => move(index, -1)}
className="bg-default text-muted hover:text-emphasis invisible flex h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all hover:border-transparent hover:shadow group-hover:visible group-hover:scale-100 ">
<ArrowUp />
</button>
) : null}
{options.length && index !== options.length - 1 ? (
<button
type="button"
onClick={() => move(index, 1)}
className="bg-default text-muted hover:text-emphasis invisible flex h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all hover:border-transparent hover:shadow group-hover:visible group-hover:scale-100 ">
<ArrowDown />
</button>
) : null}
</div>
<div className="w-full">
<TextField
disabled={!!router}
containerClassName="[&>*:first-child]:border [&>*:first-child]:border-default hover:[&>*:first-child]:border-gray-400"
className="border-0 focus:ring-0 focus:ring-offset-0"
labelSrOnly
placeholder={field.placeholder.toString()}
value={field.value}
type="text"
addOnClassname="bg-transparent border-0"
onChange={(e) => handleChange(e, index)}
addOnSuffix={
<button
type="button"
onClick={() => handleRemoveOptions(index)}
aria-label={t("remove")}>
<X className="h-4 w-4" />
</button>
}
/>
</div>
</li>
))}
</ul>
<div className={classNames("flex")}>
<Button
data-testid="add-attribute"