forked from github/plane
fix: icon picker not working (#1080)
* fix: icon picker not working * fix: project icon in analytics sidebar
This commit is contained in:
parent
e3a114cd69
commit
7f5fdb9589
@ -153,12 +153,21 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div key={project.id}>
|
<div key={project.id}>
|
||||||
<h5 className="text-sm flex items-center gap-1">
|
<h5 className="text-sm flex items-center gap-1">
|
||||||
{project.icon ? (
|
{project.emoji ? (
|
||||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">
|
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">
|
||||||
{String.fromCodePoint(parseInt(project.icon))}
|
{String.fromCodePoint(parseInt(project.emoji))}
|
||||||
</span>
|
</span>
|
||||||
|
) : project.icon_prop ? (
|
||||||
|
<div className="h-6 w-6 grid place-items-center flex-shrink-0">
|
||||||
|
<span
|
||||||
|
style={{ color: project.icon_prop.color }}
|
||||||
|
className="material-symbols-rounded text-lg"
|
||||||
|
>
|
||||||
|
{project.icon_prop.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="grid h-8 w-8 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
<span className="grid h-6 w-6 mr-1 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||||
{project?.name.charAt(0)}
|
{project?.name.charAt(0)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -254,12 +263,21 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
) : (
|
) : (
|
||||||
<div className="hidden md:flex md:flex-col h-full overflow-y-auto">
|
<div className="hidden md:flex md:flex-col h-full overflow-y-auto">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{projectDetails?.icon ? (
|
{projectDetails?.emoji ? (
|
||||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">
|
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">
|
||||||
{String.fromCodePoint(parseInt(projectDetails.icon))}
|
{String.fromCodePoint(parseInt(projectDetails.emoji))}
|
||||||
</span>
|
</div>
|
||||||
|
) : projectDetails?.icon_prop ? (
|
||||||
|
<div className="h-6 w-6 grid place-items-center flex-shrink-0">
|
||||||
|
<span
|
||||||
|
style={{ color: projectDetails.icon_prop.color }}
|
||||||
|
className="material-symbols-rounded text-lg"
|
||||||
|
>
|
||||||
|
{projectDetails.icon_prop.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="grid h-8 w-8 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
<span className="grid h-6 w-6 mr-1 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||||
{projectDetails?.name.charAt(0)}
|
{projectDetails?.name.charAt(0)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -25,13 +25,7 @@ const tabOptions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const EmojiIconPicker: React.FC<Props> = ({
|
const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorChange }) => {
|
||||||
label,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onIconColorChange,
|
|
||||||
onIconsClick,
|
|
||||||
}) => {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@ -181,13 +175,13 @@ const EmojiIconPicker: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<hr className="mb-1 h-[1px] w-full border-brand-base" />
|
<hr className="mb-1 h-[1px] w-full border-brand-base" />
|
||||||
<div className="mt-1 ml-1 grid grid-cols-8 gap-x-2 gap-y-3">
|
<div className="mt-1 ml-1 grid grid-cols-8 gap-x-2 gap-y-3">
|
||||||
{icons.material_rounded.map((icon) => (
|
{icons.material_rounded.map((icon, index) => (
|
||||||
<button
|
<button
|
||||||
|
key={`${icon.name}-${index}`}
|
||||||
type="button"
|
type="button"
|
||||||
className="mb-1 flex h-4 w-4 select-none items-center text-lg"
|
className="mb-1 flex h-4 w-4 select-none items-center text-lg"
|
||||||
key={icon.name}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onIconsClick) onIconsClick(icon.name);
|
onChange({ name: icon.name, color: activeColor });
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
10
apps/app/components/emoji-icon-picker/types.d.ts
vendored
10
apps/app/components/emoji-icon-picker/types.d.ts
vendored
@ -1,7 +1,13 @@
|
|||||||
export type Props = {
|
export type Props = {
|
||||||
label: string | React.ReactNode;
|
label: string | React.ReactNode;
|
||||||
value: any;
|
value: any;
|
||||||
onChange: (data: any) => void;
|
onChange: (
|
||||||
onIconsClick?: (data: any) => void;
|
data:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
onIconColorChange?: (data: any) => void;
|
onIconColorChange?: (data: any) => void;
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,7 @@ const defaultValues: Partial<IProject> = {
|
|||||||
identifier: "",
|
identifier: "",
|
||||||
description: "",
|
description: "",
|
||||||
network: 2,
|
network: 2,
|
||||||
icon: getRandomEmoji(),
|
emoji_and_icon: getRandomEmoji(),
|
||||||
cover_image:
|
cover_image:
|
||||||
"https://images.unsplash.com/photo-1575116464504-9e7652fddcb3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyODUyNTV8MHwxfHNlYXJjaHwxOHx8cGxhbmV8ZW58MHx8fHwxNjgxNDY4NTY5&ixlib=rb-4.0.3&q=80&w=1080",
|
"https://images.unsplash.com/photo-1575116464504-9e7652fddcb3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyODUyNTV8MHwxfHNlYXJjaHwxOHx8cGxhbmV8ZW58MHx8fHwxNjgxNDY4NTY5&ixlib=rb-4.0.3&q=80&w=1080",
|
||||||
};
|
};
|
||||||
@ -113,8 +113,14 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const onSubmit = async (formData: IProject) => {
|
const onSubmit = async (formData: IProject) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
|
const { emoji_and_icon, ...payload } = formData;
|
||||||
|
|
||||||
|
if (typeof formData.emoji_and_icon === "object") payload.icon_prop = formData.emoji_and_icon;
|
||||||
|
else payload.emoji = formData.emoji_and_icon;
|
||||||
|
|
||||||
await projectServices
|
await projectServices
|
||||||
.createProject(workspaceSlug as string, formData)
|
.createProject(workspaceSlug as string, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate<IProject[]>(
|
mutate<IProject[]>(
|
||||||
PROJECTS_LIST(workspaceSlug as string),
|
PROJECTS_LIST(workspaceSlug as string),
|
||||||
@ -164,7 +170,7 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-[#131313] bg-opacity-50 transition-opacity" />
|
<div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-20 overflow-y-auto">
|
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||||
@ -213,12 +219,31 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
|
|||||||
<div className="mt-5 space-y-4 px-4 py-3">
|
<div className="mt-5 space-y-4 px-4 py-3">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex items-center gap-x-2">
|
||||||
<div>
|
<div>
|
||||||
<EmojiIconPicker
|
<Controller
|
||||||
label={String.fromCodePoint(parseInt(watch("icon")))}
|
name="emoji_and_icon"
|
||||||
onChange={(emoji) => {
|
control={control}
|
||||||
setValue("icon", emoji);
|
render={({ field: { value, onChange } }) => (
|
||||||
}}
|
<EmojiIconPicker
|
||||||
value={watch("icon")}
|
label={
|
||||||
|
value ? (
|
||||||
|
typeof value === "object" ? (
|
||||||
|
<span
|
||||||
|
style={{ color: value.color }}
|
||||||
|
className="material-symbols-rounded text-lg"
|
||||||
|
>
|
||||||
|
{value.name}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
String.fromCodePoint(parseInt(value))
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
"Icon"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -185,11 +185,18 @@ export const SingleProjectCard: React.FC<ProjectCardProps> = ({
|
|||||||
<a>
|
<a>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<h3 className="text-1.5xl font-medium text-brand-base">{project.name}</h3>
|
<h3 className="text-1.5xl font-medium text-brand-base">{project.name}</h3>
|
||||||
{project.icon && (
|
{project.emoji ? (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||||
{String.fromCodePoint(parseInt(project.icon))}
|
{String.fromCodePoint(parseInt(project.emoji))}
|
||||||
</span>
|
</span>
|
||||||
)}
|
) : project.icon_prop ? (
|
||||||
|
<span
|
||||||
|
style={{ color: project.icon_prop.color }}
|
||||||
|
className="material-symbols-rounded text-lg"
|
||||||
|
>
|
||||||
|
{project.icon_prop.name}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-3.5 mb-7 break-all">
|
<p className="mt-3.5 mb-7 break-all">
|
||||||
{truncateText(project.description ?? "", 100)}
|
{truncateText(project.description ?? "", 100)}
|
||||||
|
@ -90,10 +90,19 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex items-center gap-x-2">
|
||||||
{project.icon ? (
|
{project.emoji ? (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||||
{String.fromCodePoint(parseInt(project.icon))}
|
{String.fromCodePoint(parseInt(project.emoji))}
|
||||||
</span>
|
</span>
|
||||||
|
) : project.icon_prop ? (
|
||||||
|
<div className="h-7 w-7 grid place-items-center">
|
||||||
|
<span
|
||||||
|
style={{ color: project.icon_prop.color }}
|
||||||
|
className="material-symbols-rounded text-lg"
|
||||||
|
>
|
||||||
|
{project.icon_prop.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||||
{project?.name.charAt(0)}
|
{project?.name.charAt(0)}
|
||||||
|
@ -66,7 +66,6 @@ export const generateBarColor = (
|
|||||||
type: "x_axis" | "segment"
|
type: "x_axis" | "segment"
|
||||||
): string => {
|
): string => {
|
||||||
let color: string | undefined = generateRandomColor(value);
|
let color: string | undefined = generateRandomColor(value);
|
||||||
console.log(value);
|
|
||||||
|
|
||||||
if (!analytics) return color;
|
if (!analytics) return color;
|
||||||
|
|
||||||
|
@ -56,27 +56,14 @@ const ControlSettings: NextPage = () => {
|
|||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
} = useForm<IProject>({ defaultValues });
|
} = useForm<IProject>({ defaultValues });
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (projectDetails)
|
|
||||||
reset({
|
|
||||||
...projectDetails,
|
|
||||||
default_assignee: projectDetails.default_assignee?.id ?? projectDetails.default_assignee,
|
|
||||||
project_lead: projectDetails.project_lead?.id ?? projectDetails.project_lead,
|
|
||||||
workspace: (projectDetails.workspace as IWorkspace).id,
|
|
||||||
});
|
|
||||||
}, [projectDetails, reset]);
|
|
||||||
|
|
||||||
const onSubmit = async (formData: IProject) => {
|
const onSubmit = async (formData: IProject) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const payload: Partial<IProject> = {
|
const payload: Partial<IProject> = {
|
||||||
name: formData.name,
|
|
||||||
network: formData.network,
|
|
||||||
identifier: formData.identifier,
|
|
||||||
description: formData.description,
|
|
||||||
default_assignee: formData.default_assignee,
|
default_assignee: formData.default_assignee,
|
||||||
project_lead: formData.project_lead,
|
project_lead: formData.project_lead,
|
||||||
icon: formData.icon,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await projectService
|
await projectService
|
||||||
.updateProject(workspaceSlug as string, projectId as string, payload)
|
.updateProject(workspaceSlug as string, projectId as string, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -94,6 +81,16 @@ const ControlSettings: NextPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectDetails)
|
||||||
|
reset({
|
||||||
|
...projectDetails,
|
||||||
|
default_assignee: projectDetails.default_assignee?.id ?? projectDetails.default_assignee,
|
||||||
|
project_lead: projectDetails.project_lead?.id ?? projectDetails.project_lead,
|
||||||
|
workspace: (projectDetails.workspace as IWorkspace).id,
|
||||||
|
});
|
||||||
|
}, [projectDetails, reset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectAuthorizationWrapper
|
<ProjectAuthorizationWrapper
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
|
@ -74,8 +74,7 @@ const GeneralSettings: NextPage = () => {
|
|||||||
if (projectDetails)
|
if (projectDetails)
|
||||||
reset({
|
reset({
|
||||||
...projectDetails,
|
...projectDetails,
|
||||||
default_assignee: projectDetails.default_assignee?.id,
|
emoji_and_icon: projectDetails.emoji ?? projectDetails.icon_prop,
|
||||||
project_lead: projectDetails.project_lead?.id,
|
|
||||||
workspace: (projectDetails.workspace as IWorkspace).id,
|
workspace: (projectDetails.workspace as IWorkspace).id,
|
||||||
});
|
});
|
||||||
}, [projectDetails, reset]);
|
}, [projectDetails, reset]);
|
||||||
@ -115,12 +114,17 @@ const GeneralSettings: NextPage = () => {
|
|||||||
network: formData.network,
|
network: formData.network,
|
||||||
identifier: formData.identifier,
|
identifier: formData.identifier,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
default_assignee: formData.default_assignee,
|
|
||||||
project_lead: formData.project_lead,
|
|
||||||
icon: formData.icon,
|
|
||||||
cover_image: formData.cover_image,
|
cover_image: formData.cover_image,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof formData.emoji_and_icon === "object") {
|
||||||
|
payload.emoji = null;
|
||||||
|
payload.icon_prop = formData.emoji_and_icon;
|
||||||
|
} else {
|
||||||
|
payload.emoji = formData.emoji_and_icon;
|
||||||
|
payload.icon_prop = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (projectDetails.identifier !== formData.identifier)
|
if (projectDetails.identifier !== formData.identifier)
|
||||||
await projectService
|
await projectService
|
||||||
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
||||||
@ -163,17 +167,34 @@ const GeneralSettings: NextPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-span-12 flex gap-2 sm:col-span-6">
|
<div className="col-span-12 flex gap-2 sm:col-span-6">
|
||||||
{projectDetails ? (
|
{projectDetails ? (
|
||||||
<Controller
|
<div className="h-7 w-7 grid place-items-center">
|
||||||
control={control}
|
<Controller
|
||||||
name="icon"
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
name="emoji_and_icon"
|
||||||
<EmojiIconPicker
|
render={({ field: { value, onChange } }) => (
|
||||||
label={value ? String.fromCodePoint(parseInt(value)) : "Icon"}
|
<EmojiIconPicker
|
||||||
value={value}
|
label={
|
||||||
onChange={onChange}
|
value ? (
|
||||||
/>
|
typeof value === "object" ? (
|
||||||
)}
|
<span
|
||||||
/>
|
style={{ color: value.color }}
|
||||||
|
className="material-symbols-rounded text-lg"
|
||||||
|
>
|
||||||
|
{value.name}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
String.fromCodePoint(parseInt(value))
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
"Icon"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Loader>
|
<Loader>
|
||||||
<Loader.Item height="46px" width="46px" />
|
<Loader.Item height="46px" width="46px" />
|
||||||
|
21
apps/app/types/projects.d.ts
vendored
21
apps/app/types/projects.d.ts
vendored
@ -9,22 +9,33 @@ import type {
|
|||||||
} from "./";
|
} from "./";
|
||||||
|
|
||||||
export interface IProject {
|
export interface IProject {
|
||||||
cover_image: string | null;
|
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
|
cover_image: string | null;
|
||||||
cycle_view: boolean;
|
cycle_view: boolean;
|
||||||
issue_views_view: boolean;
|
|
||||||
module_view: boolean;
|
|
||||||
page_view: boolean;
|
|
||||||
default_assignee: IUser | string | null;
|
default_assignee: IUser | string | null;
|
||||||
description: string;
|
description: string;
|
||||||
|
emoji: string | null;
|
||||||
|
emoji_and_icon:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
| null;
|
||||||
estimate: string | null;
|
estimate: string | null;
|
||||||
icon: string;
|
icon_prop: {
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
} | null;
|
||||||
id: string;
|
id: string;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
is_favorite: boolean;
|
is_favorite: boolean;
|
||||||
|
issue_views_view: boolean;
|
||||||
|
module_view: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
network: number;
|
network: number;
|
||||||
|
page_view: boolean;
|
||||||
project_lead: IUser | string | null;
|
project_lead: IUser | string | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
total_cycles: number;
|
total_cycles: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user