Skip to content

Commit

Permalink
feat: Initial Auto-Approval concept
Browse files Browse the repository at this point in the history
  • Loading branch information
augustuen committed Dec 29, 2024
1 parent 66948b4 commit f9982a5
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 0 deletions.
10 changes: 10 additions & 0 deletions server/lib/autoapproval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface AutoApprovalRule {
name: string;
conditions: AutoApprovalCondition[];
}

export interface AutoApprovalCondition {
implementation: string;
comparisonType: string;
value;
}
177 changes: 177 additions & 0 deletions src/components/ApprovalRuleList/RuleModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import Button from '@app/components/Common/Button';
import Modal from '@app/components/Common/Modal';
import Table from '@app/components/Common/Table';
import { Transition } from '@headlessui/react';
import { PlusIcon } from '@heroicons/react/24/solid';
import type { AutoApprovalRule } from '@server/lib/autoapproval';
import { useState } from 'react';
import Select from 'react-select';

type OptionType = { value: number; label: string; exists: boolean };

interface RuleModalProps {
approvalRule: AutoApprovalRule | null;
onClose: () => void;
onSave: () => void;
}

const GenreCondition = (comparison = 'is', values) => {
const genreOptions = [
{ label: 'Action', value: 0 },
{ label: 'Comedy', value: 1 },
{ label: 'Documentary', value: 2 },
{ label: 'Romance', value: 3 },
{ label: 'Drama', value: 4 },
];
return (
<div>
<Table.TD>
<select
id="condition-type"
name="condition-type"
className=""
defaultValue={comparison}
>
<option value="is">is</option>
<option value="isnot">is not</option>
<option value="contains">contains</option>
<option value="containsnot">does not contain</option>
</select>
</Table.TD>
<Table.TD>
<Select<OptionType, true>
options={genreOptions}
isMulti
className="react-select-container rounded-r-only"
classNamePrefix="react-select"
defaultValue={genreOptions.filter((genre) =>
typeof values == 'number'
? genre.value == values
: values.includes(genre.value)
)}
/>
</Table.TD>
</div>
);
};

const ReleaseYearCondition = (comparison = 'equals') => {
const options = [
{ value: 'equals', text: '=' },
{ value: 'greaterthan', text: '>' },
{ value: 'lessthan', text: '<' },
];
const optionsContent = options.map((option) => (
<option key={`condition-release-`} value={option.value}>
{option.text}
</option>
));
return (
<select
key="mykey"
id="comparison-type"
name="comparison-type"
className="rounded-r-only"
defaultValue={comparison}
>
{optionsContent}
</select>
);
};

const ConditionItem = (
defaultImplementation: string,
comparison = '',
values: any
) => {
const [implementation, setImplementation] = useState(defaultImplementation);
return (
<tr
key="approval-rule-condition-0"
data-testid="approval-condition-list-row"
>
<Table.TD>
<select
id="implementation"
name="implementation"
value={implementation}
onChange={(e) => setImplementation(e.target.value)}
>
<option value="genre">Genre</option>
<option value="release-year">Release Year</option>
</select>
</Table.TD>
<Table.TD>
{
{
genre: GenreCondition(comparison, values),
'release-year': ReleaseYearCondition(comparison),
}[implementation]
}
</Table.TD>
</tr>
);
};

const RuleModal = ({ onClose, approvalRule }: RuleModalProps) => {
const conditionsList = approvalRule?.conditions.map((condition) =>
ConditionItem(
condition.implementation,
condition.comparisonType,
condition.value
)
);
return (
<Transition
as="div"
appear
show
enter="transition-opacity ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Modal
onCancel={onClose}
okButtonType="primary"
okText="Add"
title="Add Auto Approval Rule"
>
<div className="mb-6">
<h2>This is a modal</h2>
</div>
<Table>
<thead>
<tr>
<Table.TH>Condition</Table.TH>
<Table.TH></Table.TH>
</tr>
</thead>
<Table.TBody>
{conditionsList}
<tr className="rounded-lg border-2 border-dashed border-gray-400 shadow">
<Table.TD
colSpan={3}
className="rounded-r-only border-2 border-dashed border-gray-400"
>
<div className="flex w-screen flex-col items-center sm:space-y-0 lg:w-full">
<Button
buttonType="ghost"
className="lg:justify-y-center flex justify-center"
>
<PlusIcon />
<span> Add Condition</span>
</Button>
</div>
</Table.TD>
</tr>
</Table.TBody>
</Table>
</Modal>
</Transition>
);
};

export default RuleModal;
203 changes: 203 additions & 0 deletions src/components/ApprovalRuleList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import RuleModal from '@app/components/ApprovalRuleList/RuleModal';
import Badge from '@app/components/Common/Badge';
import Button from '@app/components/Common/Button';
import Header from '@app/components/Common/Header';
import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
import type { AutoApprovalRule } from '@server/lib/autoapproval';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';

export const messages = defineMessages('components.ApprovalRuleList',{
autoapprovalrules: 'Auto Approval Rules',
addRule: 'Add Approval Rule',
});

interface ApprovalRuleInstanceProps {
name: string;
currentRule: AutoApprovalRule;
onEdit: () => void;
}

const ApprovalRuleInstance = ({
name,
currentRule,
onEdit,
}: ApprovalRuleInstanceProps) => {
const comparisonNames = new Map<string, string>([
['equals', '='],
['greaterthan', '>'],
['lessthan', '<'],
['is', 'is'],
['isnot', 'is not'],
['contains', 'contains'],
['does not contain'],
]);
const valueNames: string[] = ['Action', 'Comedy', 'Documentary', 'Romance'];
const conditionBadges = currentRule.conditions.map((condition) => (
<Badge key={`auto-approval-badge-`} className="m-0.5">
{condition.implementation} {comparisonNames.get(condition.comparisonType)}{' '}
{condition.value > 1000 ? condition.value : valueNames[condition.value]}
</Badge>
));

return (
<li className="col-span-1 rounded-lg bg-gray-800 shadow ring-1 ring-gray-500">
<div className="flex w-full items-center justify-between space-x-6 p-6">
<div className="flex-1 truncate">
<div className="mb-2 flex items-center space-x-2">
<h3 className="truncate font-medium leading-5 text-white">
<a
href=""
className="transition duration-300 hover:text-white hover:underline"
>
{name}
</a>
</h3>
</div>
<p className="mt-1 flex flex-wrap text-sm leading-5 text-gray-300">
{conditionBadges}
</p>
</div>
</div>
<div className="border-t border-gray-500">
<div className="-mt-px flex">
<div className="flex w-0 flex-1 border-r border-gray-500">
<button
onClick={() => onEdit()}
className="focus:ring-blue relative -mr-px inline-flex w-0 flex-1 items-center justify-center rounded-bl-lg border border-transparent py-4 text-sm font-medium leading-5 text-gray-200 transition duration-150 ease-in-out hover:text-white focus:z-10 focus:border-gray-500 focus:outline-none"
>
<PencilIcon className="mr-2 h-5 w-5" />
<span>Edit</span>
</button>
</div>
<div className="-ml-px flex w-0 flex-1">
<button className="focus:ring-blue relative inline-flex w-0 flex-1 items-center justify-center rounded-br-lg border border-transparent py-4 text-sm font-medium leading-5 text-gray-200 transition duration-150 ease-in-out hover:text-white focus:z-10 focus:border-gray-500 focus:outline-none">
<TrashIcon className="mr-2 h-5 w-5" />
<span>Delete</span>
</button>
</div>
</div>
</div>
</li>
);
};

const AutoApprovalList = () => {
const intl = useIntl();
const movieRuleData = [
{
name: 'Test Rule',
currentRule: {
name: 'Test Rule',
conditions: [
{ implementation: 'genre', comparisonType: 'is', value: 0 },
{
implementation: 'release-year',
comparisonType: 'lessthan',
value: 2020,
},
{ implementation: 'genre', comparisonType: 'isnot', value: 2 },
{
implementation: 'genre',
comparisonType: 'contains',
value: [1, 4],
},
],
},
},
];
const [editRuleModal, setEditRuleModal] = useState<{
open: boolean;
approvalRule: AutoApprovalRule | null;
}>({
open: false,
approvalRule: null,
});
return (
<div className="mt-6">
<div className="mt-10 text-white">
<div className="flex flex-col justify-between lg:flex-row lg:items-end">
<Header>Auto Approval Rules</Header>
</div>
{editRuleModal.open && (
<RuleModal
approvalRule={editRuleModal.approvalRule}
onClose={() =>
setEditRuleModal({ open: false, approvalRule: null })
}
onSave={() => {
setEditRuleModal({ open: false, approvalRule: null });
}}
/>
)}
<h3 className="heading">Movie Auto-approval rules</h3>
<div className="section">
<ul className="xl:grid-cols3 grid max-w-4xl grid-cols-1 gap-6 lg:grid-cols-2">
{movieRuleData.map((rule) => (
<ApprovalRuleInstance
key={`approval-rule-`}
name={rule.name}
onEdit={() =>
setEditRuleModal({
open: true,
approvalRule: rule.currentRule,
})
}
currentRule={{
name: rule.name,
conditions: rule.currentRule.conditions,
}}
/>
))}
<li className="col-span-1 h-32 rounded-lg border-2 border-dashed border-gray-400 shadow sm:h-44">
<div className="flex h-full w-full items-center justify-center">
<Button
buttonType="ghost"
className="mt-3 mb-3"
onClick={() =>
setEditRuleModal({
open: true,
approvalRule: {
name: 'Test rule',
conditions: [
{
implementation: 'genre',
comparisonType: 'is',
value: 4,
},
{
implementation: 'release-year',
comparisonType: 'lessthan',
value: 2,
},
],
},
})
}
>
<PlusIcon />
<span>Add Rule</span>
</Button>
</div>
</li>
</ul>
</div>
<h3 className="heading">Series Auto-approval rules</h3>
<div className="section">
<ul className="xl:grid-cols3 grid max-w-6xl grid-cols-1 gap-6 lg:grid-cols-2">
<li className="col-span-1 h-32 rounded-lg border-2 border-dashed border-gray-400 shadow sm:h-44">
<div className="flex h-full w-full items-center justify-center">
<Button buttonType="ghost" className="mt-3 mb-3">
<PlusIcon />
<span>Add Rule</span>
</Button>
</div>
</li>
</ul>
</div>
</div>
</div>
);
};

export default AutoApprovalList;
Loading

0 comments on commit f9982a5

Please sign in to comment.