-
-
Notifications
You must be signed in to change notification settings - Fork 278
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
407 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.