v2.3.1
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
.text.wx-4RuF7aAO {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.text.wx-4RuF7aAO:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--wx-color-link);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { context } from '@svar-ui/react-core';
|
||||
import './AddTaskCell.css';
|
||||
|
||||
export default function AddTaskCell() {
|
||||
const i18n = useContext(context.i18n);
|
||||
const _ = useMemo(() => i18n.getGroup('gantt'), []);
|
||||
const text = useMemo(() => _('Add task'), []);
|
||||
|
||||
return (
|
||||
<div className="wx-4RuF7aAO text" data-action="add-task">
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useMemo } from 'react';
|
||||
import UserStub from './UserStub.jsx';
|
||||
import { users } from '../data';
|
||||
|
||||
function AvatarCell(props) {
|
||||
const { row } = props;
|
||||
|
||||
const user = useMemo(
|
||||
() => users.find((u) => u.id == row.assigned),
|
||||
[row.assigned, users],
|
||||
);
|
||||
|
||||
return <UserStub user={user} />;
|
||||
}
|
||||
|
||||
export default AvatarCell;
|
||||
@@ -0,0 +1,66 @@
|
||||
.backdrop.wx-QkE5vh0y {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
background: var(--wx-modal-backdrop);
|
||||
}
|
||||
|
||||
.modal.wx-QkE5vh0y {
|
||||
position: relative;
|
||||
width: 340px;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
box-shadow:
|
||||
0 4px 4px rgba(0, 0, 0, 0.12),
|
||||
0 0 10px rgba(0, 0, 0, 0.06);
|
||||
background-color: var(--wx-background);
|
||||
font-family: var(--wx-font-family);
|
||||
font-size: var(--wx-font-size);
|
||||
color: var(--wx-color-font);
|
||||
}
|
||||
|
||||
.title.wx-QkE5vh0y {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close.wx-QkE5vh0y {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
transition: color 0.15s ease-in;
|
||||
}
|
||||
|
||||
.close.wx-QkE5vh0y:hover {
|
||||
color: rgb(255, 122, 122);
|
||||
}
|
||||
|
||||
.body.wx-QkE5vh0y {
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
|
||||
.button.wx-QkE5vh0y {
|
||||
padding: 10px;
|
||||
margin: 1.5em 0 0 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
font-family: var(--wx-font-family);
|
||||
font-size: var(--wx-font-size);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button.wx-QkE5vh0y:focus {
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.danger.wx-QkE5vh0y {
|
||||
color: var(--wx-color-danger-font);
|
||||
background-color: var(--wx-color-danger);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Field,
|
||||
Text,
|
||||
TextArea,
|
||||
Select,
|
||||
Slider,
|
||||
DatePicker,
|
||||
} from '@svar-ui/react-core';
|
||||
import { useWritableProp } from '@svar-ui/lib-react';
|
||||
import './Form.css';
|
||||
|
||||
export default function Form(props) {
|
||||
const [task, setTask] = useWritableProp(props.task);
|
||||
const { taskTypes, onAction } = props;
|
||||
|
||||
const nodeRef = useRef(null);
|
||||
const [left, setLeft] = useState();
|
||||
const [top, setTop] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef.current) {
|
||||
setLeft((window.innerWidth - nodeRef.current.offsetWidth) / 2);
|
||||
setTop((window.innerHeight - nodeRef.current.offsetHeight) / 2);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function deleteTask() {
|
||||
onAction && onAction({ action: 'delete-task', data: { id: task.id } });
|
||||
onAction && onAction({ action: 'close-form' });
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
onAction && onAction({ action: 'close-form' });
|
||||
}
|
||||
|
||||
function handleChange({ value }, key) {
|
||||
let t = task;
|
||||
if (key === 'type' && value === 'milestone') {
|
||||
delete t.end;
|
||||
t.duration = 0;
|
||||
} else if (t.start > t.end) {
|
||||
t.start = t.end;
|
||||
t.duration = 1;
|
||||
t.end = 0;
|
||||
}
|
||||
t = {
|
||||
...t,
|
||||
[key]: value,
|
||||
};
|
||||
setTask(t);
|
||||
onAction &&
|
||||
onAction({
|
||||
action: 'update-task',
|
||||
data: { id: t.id, task: t },
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wx-QkE5vh0y backdrop">
|
||||
<div className="wx-QkE5vh0y modal" style={{ left, top }} ref={nodeRef}>
|
||||
<div className="wx-QkE5vh0y header">
|
||||
<h3 className="wx-QkE5vh0y title">Edit task</h3>
|
||||
<i className="wx-QkE5vh0y close wxi-close" onClick={onClose}></i>
|
||||
</div>
|
||||
<div className="wx-QkE5vh0y body">
|
||||
<Field label="Name">
|
||||
{({ id }) => (
|
||||
<Text
|
||||
id={id}
|
||||
focus={true}
|
||||
value={task.text}
|
||||
onChange={(ev) => handleChange(ev, 'text')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field label="Description">
|
||||
{({ id }) => (
|
||||
<TextArea
|
||||
id={id}
|
||||
value={task.details}
|
||||
onChange={(ev) => handleChange(ev, 'details')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{taskTypes.length > 1 ? (
|
||||
<Field label="Type">
|
||||
{({ id }) => (
|
||||
<Select
|
||||
id={id}
|
||||
value={task.type}
|
||||
options={taskTypes}
|
||||
onChange={(ev) => handleChange(ev, 'type')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
) : null}
|
||||
|
||||
<Field label="Start date">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
id={id}
|
||||
value={task.start}
|
||||
onChange={(ev) => handleChange(ev, 'start')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{task.type !== 'milestone' ? (
|
||||
<>
|
||||
<Field label="End date">
|
||||
{({ id }) => (
|
||||
<DatePicker
|
||||
id={id}
|
||||
value={task.end}
|
||||
onChange={(ev) => handleChange(ev, 'end')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field label={`Progress: ${task.progress}%`}>
|
||||
{({ id }) => (
|
||||
<Slider
|
||||
id={id}
|
||||
value={task.progress}
|
||||
onChange={(ev) => handleChange(ev, 'progress')}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<button className="wx-QkE5vh0y button danger" onClick={deleteTask}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
button.wx-BzRGIq8x {
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
font: var(--wx-gantt-bar-font);
|
||||
}
|
||||
|
||||
.text-right.wx-BzRGIq8x {
|
||||
left: 100%;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.text-left.wx-BzRGIq8x {
|
||||
right: 100%;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.text-right.wx-BzRGIq8x,
|
||||
.text-left.wx-BzRGIq8x,
|
||||
button.wx-BzRGIq8x {
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import './MyTaskContent.css';
|
||||
|
||||
function MyTaskContent({ data, onAction }) {
|
||||
function doClick(ev) {
|
||||
ev.stopPropagation();
|
||||
onAction({
|
||||
action: 'custom-click',
|
||||
data: {
|
||||
clicked: !data.clicked,
|
||||
id: data.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.type !== 'milestone' ? (
|
||||
<>
|
||||
<div className="wx-BzRGIq8x wx-text-out text-right">
|
||||
{data.text || ''}
|
||||
</div>
|
||||
<button className="wx-BzRGIq8x" onClick={doClick}>
|
||||
{data.clicked ? 'Was clicked' : 'Click Me'}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="wx-BzRGIq8x wx-text-out text-left">
|
||||
{data.text || ''}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyTaskContent;
|
||||
@@ -0,0 +1,21 @@
|
||||
.data.wx-SfydHtKO {
|
||||
white-space: nowrap;
|
||||
background-color: var(--wx-tooltip-background);
|
||||
padding: 3px 8px;
|
||||
}
|
||||
|
||||
.text.wx-SfydHtKO {
|
||||
font-family: var(--wx-font-family);
|
||||
color: var(--wx-color-primary-font);
|
||||
font-size: 13px;
|
||||
text-transform: capitalize;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.text:last-child.wx-SfydHtKO {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.caption.wx-SfydHtKO {
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { format } from 'date-fns';
|
||||
import './MyTooltipContent.css';
|
||||
|
||||
function MyTooltipContent(props) {
|
||||
const { data } = props;
|
||||
|
||||
const mask = 'yyyy.MM.dd';
|
||||
return (
|
||||
<>
|
||||
{data ? (
|
||||
<div className="wx-SfydHtKO data">
|
||||
<div className="wx-SfydHtKO text">
|
||||
<span className="wx-SfydHtKO caption">{data.type}: </span>
|
||||
{data.text}
|
||||
</div>
|
||||
<div className="wx-SfydHtKO text">
|
||||
<span className="wx-SfydHtKO caption">start: </span>
|
||||
{format(data.start, mask)}
|
||||
</div>
|
||||
{data.end ? (
|
||||
<div className="wx-SfydHtKO text">
|
||||
<span className="wx-SfydHtKO caption">end: </span>
|
||||
{format(data.end, mask)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyTooltipContent;
|
||||
@@ -0,0 +1,10 @@
|
||||
.text.wx-4qJ64Gcp,
|
||||
.date.wx-4qJ64Gcp {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.date.wx-4qJ64Gcp {
|
||||
color: #5f5f5f;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useMemo } from 'react';
|
||||
import './NameAndDateCell.css';
|
||||
|
||||
export default function NameAndDateCell({ row }) {
|
||||
const date = useMemo(() => row.start.toLocaleDateString(), [row.start]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="wx-4qJ64Gcp text">{row.text}</div>
|
||||
<div className="wx-4qJ64Gcp date">{date}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.container.wx-Md6hA5Em {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.avatar.wx-Md6hA5Em {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.user-avatar.wx-Md6hA5Em {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background-color: #dfe2e6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-photo.wx-Md6hA5Em {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: #dfe2e6;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useMemo } from 'react';
|
||||
import './UserStub.css';
|
||||
|
||||
function UserStub(props) {
|
||||
const { user } = props;
|
||||
|
||||
const url = 'https://svar.dev/demos/grid/assets/avatars/';
|
||||
const avatar = useMemo(
|
||||
() => (user ? `${url}${user.label.replace(' ', '_')}.png` : ''),
|
||||
[user],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="wx-Md6hA5Em container">
|
||||
<div className="wx-Md6hA5Em avatar">
|
||||
{avatar ? (
|
||||
<div className="wx-Md6hA5Em user-avatar">
|
||||
<img className="wx-Md6hA5Em user-photo" alt="" src={avatar} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div>{user?.label ?? ''}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserStub;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Combo } from '@svar-ui/react-core';
|
||||
import UserStub from './UserStub.jsx';
|
||||
|
||||
function UsersCustomCombo(props) {
|
||||
const { value, options, onChange } = props;
|
||||
|
||||
return (
|
||||
<Combo
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
clear
|
||||
placeholder="Assign to the person"
|
||||
>
|
||||
{({ option }) => <UserStub user={option} />}
|
||||
</Combo>
|
||||
);
|
||||
}
|
||||
|
||||
export default UsersCustomCombo;
|
||||
Reference in New Issue
Block a user